Индекс доступа в цикле диапазона
У меня есть вектор объектов, и я повторяю его с помощью цикла range-for. Я использую его для печати функции из объекта, например:
vector<thisObject> storedValues;
//put stuff in storedValues
for(auto i:storedValues)
{
cout<<i.function();
}
Но я тоже хочу напечатать индекс. Мой желаемый результат:
1: value
2: value
//etc
Я собирался использовать счетчик, который я увеличивал каждый раз, но это казалось очень неэффективным. Есть ли лучший способ?
Ответы
Ответ 1
Вы не можете. Индекс является специфическим понятием для вектора, а не общим свойством коллекции. С другой стороны, петля на основе диапазона - это общий механизм для итерации по каждому элементу любой коллекции.
Если вы хотите использовать детали своей конкретной реализации контейнера, просто используйте обычный цикл:
for (std::size_t i = 0, e = v.size(); i != e; ++i) { /* ... */ }
Повторить точку: петли на основе диапазона предназначены для управления каждым элементом любой коллекции, где сама коллекция не имеет значения, и контейнер никогда не упоминается внутри тела цикла. Это просто еще один инструмент в вашем наборе инструментов, и вы не должны использовать его абсолютно для всех. В отличие от этого, если вы хотите либо мутировать коллекцию (например, удалить или перетасовать элементы), либо использовать определенную информацию о структуре коллекции, используйте обычный цикл.
Ответ 2
Я уверен, что некоторым это не понравится, но я создал макрос препроцессора, который обрабатывает это для вас (IMO) относительно чисто:
#define for_indexed(...) for_indexed_v(i, __VA_ARGS__)
#define for_indexed_v(v, ...) for(bool _i_ = true, _break_ = false; _i_;) for(size_t v = 0; _i_; _i_ = false) for(__VA_ARGS__) if(_break_) break; else for(bool _j_ = true; _j_;) for(_break_ = true; _j_; _j_ = false) for(bool _k_ = true; _k_; v++, _k_ = false, _break_ = false)
Пример использования:
std::vector<int> v {1, 2, 3};
for_indexed (const auto& item : v) {
if (i > 0) std::cout << ", ";
std::cout << i << ": " << item;
}
Чтобы использовать другую переменную цикла:
for_indexed_v (my_cool_counter, const auto& item : v) ...
Я дважды проверил, и все эти лишние -O1
оптимизированы для -O1
или выше. Вы остались с хорошим, легко читаемым синтаксисом цикла.
Бонус: это работает и для классических циклов iterator
-style. (Хотя вы также можете использовать std::distance(begin, it)
.)
Обновление 28.05.2017: Сделан break;
заявления работают правильно
Обновление 28/01/2019: Положите for
в макроимени так, что слово indexed
является допустимым именем переменной. Я сомневаюсь, что for_indexed
вызовет какие-либо конфликты.
Ответ 3
Используйте range-v3
. Range-v3
- это библиотека диапазонов следующего поколения, разработанная и реализованная членом комитета ISO C++ Эриком Ниблером и предназначенная для использования в стандарте C++ в будущем.
С помощью range-v3
OP проблему легко решить:
using ranges::v3::view::zip;
using ranges::v3::view::ints;
for(auto &&[i, idx]: zip(storedValues, ints(0u))){
std::cout << idx << ": " << i.function() << '\n';
}
Вам понадобится компилятор, который поддерживает C++ 17 или более позднюю версию, чтобы скомпилировать этот фрагмент кода, не только для синтаксиса структурированной привязки, но и для факта, что возвращаемый тип функций begin
и end
для возвращаемого значения ranges::v3::view::zip
отличаются.
Вы можете увидеть онлайн пример здесь. Документация по range-v3
находится здесь, а сам исходный код размещен здесь. Вы также можете посмотреть здесь, если вы используете компиляторы MSVC.
Ответ 4
Это довольно просто, честно говоря, просто нужно выяснить, что вы можете вычесть адреса :)
& i будет ссылаться на адрес в памяти, и он будет увеличиваться на 4 от индекса к индексу, потому что он содержит целое число из определенного типа вектора. Теперь & values [0] ссылается на первую точку, когда вы вычитаете 2 адреса, разница между ними будет 0,4,8,12 соответственно, но в действительности это вычитание размера целочисленного типа, который обычно составляет 4 байта. Таким образом, в соответствии 0 = 0-й int, 4 = 1-й int, 8 = 2-й int, 12 = 3-й int
Вот оно в векторе
vector<int> values = {10,30,9,8};
for(auto &i: values) {
cout << "index: " << &i - &values[0];
cout << "\tvalue: " << i << endl;
}
Вот это для обычного массива, почти то же самое
int values[]= {10,30,9,8};
for(auto &i: values) {
cout << "index: " << &i - &values[0];
cout << "\tvalue: " << i << endl;
}
Обратите внимание, что это для С++ 11, если вы используете g++, не забудьте использовать параметр -std = С++ 11 для компиляции
Ответ 5
Здесь обновленная версия макропрограммы препроцессора для С++ 17. Clang Генерирует идентичный отладочный и оптимизированный код по сравнению с написанием цикла с индексом вручную. MSVC генерирует идентичный оптимизированный код, но добавляет несколько дополнительных инструкций при отладке.
#define for_index(...) for_index_v(i, __VA_ARGS__)
#define for_index_v(i, ...) if (size_t i##_next = 0; true) for (__VA_ARGS__) if (size_t i = i##_next++; true)
Я пытался покопаться в дополнительном фрагменте кода, который MSVC добавлял в отладочные сборки, я не совсем уверен, какова его цель. В начале цикла добавляется следующее:
xor eax, eax
cmp eax, 1
je [email protected]_i
Который пропустит цикл полностью. Используемый пример: https://godbolt.org/z/VTWhgT