7

У меня есть простейший Range-based цикл:

int arr[3] = { 9, 7, 1 };
for (auto i: arr) std::cout << i << "\n";

Программа выведет на экран содержимое массива:

9
7
1

А если я хочу вывести на экран более подробную информацию:

массив[0] = 9
массив[1] = 7
массив[2] = 1

Как мне обойтись без введения дополнительной переменной-счетчика? или это невозможно?

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
MikeNew
  • 317

3 Answers3

8

Очевидно, в этом предложении

for (auto i: arr) std::cout << "i" << "\n";
                              ^^^^

вы допустили опечатку. Скорей всего вы имели в виду

for (auto i: arr) std::cout << i << "\n";
                              ^^^^

Что касается вашего вопроса, то чтобы дополнительно к значениям элементов массива получить значения индексов элементов массива, вам в любом случае придется вводить дополнительную переменную.

Поэтому в таких случаях лучше использовать обычное for предложение.

for ( size_t i = 0; i < sizeof( arr ) / sizeof( *arr ); i++ )
{
    std::cout << arr[i] << "\n";
}

Циклы на основе диапазона для того и введены, чтобы освободиться от переменной, которая обычно при выводе значений массива не нужна. Использование переменной для хранения индексов часто является причиной многочисленных ошибок либо когда индексирование начинают не с 0, а, например, с 1, или когда указывают неправильно верхнее значение диапазона индексов.

6

В дополнение к и так полному ответу @Vlad from Moscow - на случай использования других контейнеров учтите, что понятие индексов применимо не ко всем контейнерам. Так что способ вывода, например,

int arr[3] = { 9, 7, 1 };
int i = 0;
for (auto n: arr) 
{
    std::cout << "arr[" << i++ << "] = " << n << "\n";
}

Который в принципе сработает для массивов или векторов (и то не уверен, гарантирует ли это стандарт), для многих контейнеров не подойдет - например, для хэш-таблиц порядок размещения вообще никак не определен, для множеств - они всегда упорядочены.

Harry
  • 221,325
  • кроме встроенного массива и std::vector , индексацию можно применять и для std::deque - последовательный контейнер хранящий данные блоками по k элементов образующие связный список – ampawd Jan 26 '17 at 14:16
6

Получить индекс элемента внутри range-for можно, но для этого надо изменить тип переменной:

#include <iostream>

int main() 
{
    int arr[3] = { 9, 7, 1 };
    for (auto& v: arr) std::cout << (&v - &arr[0]) << "-" <<  v << "\n";
}

Вывод:

0-9   
1-7   
2-1

Здесь мы добавили & к auto, чтобы тип итерируемой переменной был int&. Т.о. переменная цикла это не новая копия, а ссылка на элемент массива. И т.к. элементы массива расположены в памяти последовательно, то выражение (&v - &arr[0]) в данном случае будет давать смещение элемента от начала массива, т.е. по сути необходимый Вам индекс.

Можно заметить, что дополнительную переменную вводить для этого не пришлось, как утверждали другие участники.

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
  • 2
    а если arr это std::vector<int>? – KoVadim Jan 17 '17 at 09:10
  • @KoVadim логично, что код немного изменится. – αλεχολυτ Jan 17 '17 at 09:24
  • 1
    а если там нечаяно станет std::list<int> ? – KoVadim Jan 17 '17 at 09:27
  • 3
    @KoVadim, тогда std::distance — наше всё. С любовью, искренне ваш маляр Шлемиэль. – Arhadthedev Jan 17 '17 at 09:28
  • @KoVadim в вопросе ТС массив, мой ответ на вопрос ТС. А что если он его сейчас изменит? Мне нужно будет удалить ответ или корректировать его? Поэтому не надо строить предположений, я не обещал работоспособный вариант для всех контейнеров. Именно потому, что в вопросе был исключительно массив. – αλεχολυτ Jan 17 '17 at 09:30
  • ну, уже пришлось скорректировать код ТС:) – KoVadim Jan 17 '17 at 09:34
  • @KoVadim давайте не будет опускаться до абсурда, а то и "i" оставить пришлось бы. – αλεχολυτ Jan 17 '17 at 09:56
  • @KoVadim, вот вектор: http://ru.stackoverflow.com/a/586593/178988 – Qwertiy Jan 17 '17 at 14:54
  • @Arhad: Впросы, конечно, абсурдные, но тем не менее std::distance здесь никак не поможет. – AnT stands with Russia Jan 17 '17 at 15:22
  • @AnT, почему? for (auto& v: arr) std::cout << std::distance(&arr[0], &v) << '-' << v << '\n';. В отличие от оператора вычитания, distance имеет шаблонную перегрузку для контейнеров без непрерывного хранения. – Arhadthedev Jan 17 '17 at 16:13
  • 2
    @Arhad: std::distance требует итераторы на вход, а не указатели на элементы контейнера. Для std::vector (как в вашем примере) это сработает, по очевидным причинам. Ни для какого другого контейнера это, разумеется, работать не будет, пока вы не найдете способа конвертировать указатели на элементы в итераторы. Прямой конверсии в общем случае не существует, по каковой причине std::distance тут ничем помочь не может. – AnT stands with Russia Jan 17 '17 at 17:36
  • А по-моему это самый правильный ответ на данный вопрос. Как раз что хотел ТС. И подход оригинальный. – Isaev Jan 17 '17 at 18:25
  • 2
    @Isaev: "Самый правильный ответ" - это не использовать range-based for в ситуациях, когда нужен индекс или итератор текущего элемента. То, что предлагается в этом ответе, приемлемо в отладочных целях, но не более того. – AnT stands with Russia Jan 17 '17 at 18:27
  • @AnT ну это уже другая сторона медали, естественно, что эта конструкция для того и была создана, но для общего развития и уталения любопытства очень даже полезно – Isaev Jan 17 '17 at 18:38