Выбрать главу

В предыдущем примере как управляющие переменные цикла использовались ссылки, поскольку элементы массива необходимо было изменять. Однако есть и более серьезная причина для использования ссылок. Рассмотрим в качестве примера следующий цикл:

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.