В предыдущем примере как управляющие переменные цикла использовались ссылки, поскольку элементы массива необходимо было изменять. Однако есть и более серьезная причина для использования ссылок. Рассмотрим в качестве примера следующий цикл:
for (const auto &row : ia) // для каждого элемента во внешнем массиве
for (auto col : row) // для каждого элемента во внутреннем массиве
cout << col << endl;
Этому циклу запись в элементы не нужна, но все же управляющая переменная внешнего цикла определена как ссылка. Это сделано для того, чтобы избежать преобразования обычного массива в указатель (см. раздел 3.5.3). Если пренебречь ссылкой и написать эти циклы так, то компиляция потерпит неудачу:
for (auto row : ia)
for (auto col : row)
Как и прежде, первый цикл for перебирает элементы массива ia, являющиеся массивами по 4 элемента. Поскольку row не ссылка, при его инициализации компилятор преобразует каждый элемент массива (как и любой другой объект типа массива) в указатель на первый элемент этого массива. В результате типом row в этом цикле будет int*. Внутренний цикл for некорректен. Несмотря на намерения разработчика, этот цикл пытается перебрать указатель типа int*.
Чтобы использовать многомерный массив в серийном операторе
for, управляющие переменные всех циклов, кроме самого внутреннего, должны быть ссылками.
Подобно любым другим массивам, имя многомерного массива автоматически преобразуется в указатель на первый его элемент.
Определяя указатель на многомерный массив, помните, что на самом деле он является массивом массивов.
Поскольку многомерный массив в действительности является массивом массивов, тип указателя, в который преобразуется массив, является типом первого внутреннего массива.
int ia[3][4]; // массив размером 3 элемента; каждый элемент - массив
// из 4 целых чисел
int (*p)[4] = ia; // p указывает на массив из четырех целых чисел
p = &ia[2]; // теперь p указывает на последний элемент ia
Применяя стратегию из раздела 3.5.1, начнем рассмотрение с части (*p), гласящей, что p — указатель. Глядя вправо, замечаем, что объект, на который указывает указатель p, имеет размер 4 элемента, а глядя влево, видим, что типом элемента является int. Следовательно, p — это указатель на массив из четырех целых чисел.
Круглые скобки в этом объявлении необходимы.
int *ip[4]; // массив указателей на int
int (*ip)[4]; // указатель на массив из четырех целых чисел
Новый стандарт зачастую позволяет избежать необходимости указывать тип указателя на массив за счет использования спецификаторов
auto и decltype (см. раздел 2.5.2).
// вывести значение каждого элемента ia; каждый внутренний массив
// отображается в отдельной строке
// p указывает на массив из четырех целых чисел
for (auto p = ia; p != ia + 3; ++p) {
// q указывает на первый элемент массива из четырех целых чисел;
// т.е. q указывает на int
for (auto q = *p; q != *p + 4; ++q)
cout << *q << ' '; cout << endl;
}
Внешний цикл for начинается с инициализации указателя p адресом первого массива в массиве ia. Этот цикл продолжается, пока не будут обработаны все три ряда массива ia. Инкремент ++p перемещает указатель p на следующий ряд (т.е. следующий элемент) массива ia.