Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
++cnt; // инкремент cnt
}
Этот цикл присваивает элементам массива ia те же значения, что и предыдущий цикл, но на сей раз управление индексами берет на себя система. Значения элементов необходимо изменить, поэтому объявляем управляющие переменные row и col как ссылки (см. раздел 3.2.3). Первый оператор for перебирает элементы массива ia, являющиеся массивами из 4 элементов. Таким образом, типом row будет ссылка на массив из четырех целых чисел. Второй цикл for перебирает каждый из этих массивов по 4 элемента. Следовательно, col имеет тип int&. На каждой итерации значение cnt присваивается следующему элементу массива ia, а затем осуществляется инкремент переменной cnt.
В предыдущем примере как управляющие переменные цикла использовались ссылки, поскольку элементы массива необходимо было изменять. Однако есть и более серьезная причина для использования ссылок. Рассмотрим в качестве примера следующий цикл:
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.
Внутренний цикл for выводит значения внутренних массивов. Он начинается с создания указателя q на первый элемент в массиве, на который указывает указатель p. Результатом *p будет массив из четырех целых чисел. Как обычно, при использовании имени массива оно автоматически преобразуется в указатель на его первый элемент. Внутренний цикл for выполняется до тех пор, пока не будет обработан каждый элемент во внутреннем массиве. Чтобы получить указатель на элемент сразу за концом внутреннего массива, мы снова обращаемся к значению указателя p, чтобы получить указатель на первый элемент в этом массиве. Затем добавляем к нему 4, чтобы обработать четыре элемента в каждом внутреннем массиве.
Конечно, используя библиотечные функции begin() и end() (см. раздел 3.5.3), этот цикл можно существенно упростить:
// p указывает на первый массив в ia
for (auto p = begin(ia); p != end(ia); ++p) {
// q указывает на первый элемент во внутреннем массиве
for (auto q = begin(*p); q != end(*p); ++q)
cout << *q << ' '; // выводит значение, указываемое q
cout << endl;
}
Спецификатор auto позволяет библиотеке самостоятельно определить конечный указатель и избавить от необходимости писать тип, значение которого возвращает функция begin(). Во внешнем цикле этот тип — указатель на массив из четырех целых чисел. Во внутреннем цикле этот тип — указатель на тип int.
Псевдонимы типов упрощают указатели на многомерные массивыПсевдоним типа (см. раздел 2.5.1) может еще больше облегчить чтение, написание и понимание указателей на многомерные массивы. Рассмотрим пример.
using int_array = int[4]; // объявление псевдонима типа нового стиля;
// см. раздел 2.5.1
typedef int int_array[4]; // эквивалентное объявление typedef;
// см. раздел 2.5.1
// вывести значение каждого элемента ia; каждый внутренний массив
// отображается в отдельной строке
for (int_array *p = ia; p != ia + 3; ++p) {
for (int *q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
Код начинается с определения int_array как имени для типа "массив из четырех целых чисел". Это имя типа используется для определения управляющей переменной внешнего цикла for.
Упражнения раздела 3.6Упражнение 3.43. Напишите три разных версии программы для вывода элементов массива ia. Одна версия должна использовать для управления перебором серийный оператор for, а другие две — обычный цикл for, но в одном случае использовать индексирование, а в другом — указатели. Во всех трех программах пишите все типы явно, т.е. не используйте псевдонимы типов и спецификаторы auto или decltype для упрощения кода.
Упражнение 3.44. Перепишите программы из предыдущего упражнения, используя псевдоним для типа управляющих переменных цикла.
Упражнение 3.45. Перепишите программы снова, на сей раз используя спецификатор auto.
Резюме
Одними из важнейших библиотечных типов являются vector и string. Строка — это последовательность символов переменной длины, а вектор — контейнер объектов единого типа.
Итераторы обеспечивают косвенный доступ к хранящимся в контейнере объектам. Итераторы используются для доступа и перемещения между элементами в строках и векторах.
Массивы и указатели на элементы массива обеспечивают низкоуровневые аналоги библиотечных типов vector и string. Как правило, предпочтительней использовать библиотечные классы, а не их низкоуровневые альтернативы, массивы и указатели, встроенные в язык.
Термины
Арифметические действия с итераторами (iterator arithmetic). Операции с итераторами векторов и строк. Добавление и вычитание целого числа из итератора приводит к изменению позиции итератора на соответствующее количество элементов вперед или назад от исходного. Вычитание двух итераторов позволяет вычислить дистанцию между ними. Арифметические действия допустимы лишь для итераторов, относящихся к элементам того же контейнера.