double rates [5] = {88.99, 100.12, 59.45, 183.11, 340.5};
double * const pc = rates; //pc указывает на начало массива
рс = &rates[2]; // не разрешено указывать на что-нибудь другое
*рс = 92.99; // все в порядке -- изменяется rates[0]
Такой указатель можно по-прежнему применять для изменения значений, но он может указывать только на ячейку, которая была присвоена первоначально.
Наконец, const можно использовать дважды, чтобы создать указатель, который не допускает изменения ни адреса, куда он указывает, ни указываемого с помощью него значения:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; const double * const pc = rates; pc = &rates[2]; // не разрешено
pc = &ra tes [92.99]; // не разрешено
Указатели и многомерные массивы
Каким образом указатели связаны с многомерными массивами? И для чего это необходимо знать? Функции, которые работают с многомерными массивами, делают это с помощью указателей, поэтому прежде чем переходить к исследованию таких функций, нужно продолжить изучение указателей. Чтобы получить ответ на первый вопрос, рассмотрим несколько примеров. Для простоты ограничимся массивом небольшого размера. Предположим, что имеется следующее объявление:
int zippo[4] [2]; /* массив из массивов типа int */
Тогда zippo, будучи именем массива, представляет собой адрес первого элемента в этом массиве. В данном случае первый элемент zippo сам является массивом из двух значений int, так что zippo — это адрес массива, содержащего два значения int.
398 глава 10
Давайте проанализируем это дополнительно в терминах свойств указателей.
• Так как zippo — адрес первого элемента массива, zippo имеет тоже значение, что и &zippo [0]. Вдобавок zippo [0] сам по себе является массивом из двух целых чисел, следовательно, zippo [0] имеет то же значение, что и & zippo [0] [0], т.е. адрес его первого элемента — значения int. Короче говоря, zippo [0] — это адрес объекта с размером значения int, a zippo — адрес объекта с размером двух значений int. Поскольку и целое число, и массив из двух целых чисел начинаются в одной и той же позиции, числовые значения zippo и zippo [0] одинаковы.
• Добавление 1 к указателю или адресу дает значение, которое больше исходного на размер указываемого объекта. В этом отношении zippo и zippo [0] отличаются друг от друга, потому что zippo ссылается на объект с размером в два значения int, a zippo [0] — на объект с размером в одно значение int. Таким образом, zippo + 1 имеет значение, не совпадающее с zippo [0] + 1.
• Разыменование указателя или адреса (применение операции * или операции[] с индексом) дает значение, представленное объектом, на который производится ссылка. Поскольку zippo [0] — адрес его первого элемента (zippo [0] [0]), то *( zippo [0]) представляет значение, хранящееся в zippo [0] [0], т.е. значение int. Аналогично, * zippo представляет значение своего первого элемента (zippo [0]), но zippo [0] сам по себе — адрес значения int. Это адрес & zippo [0] [0], так что * zippo является Szippo [0] [0]. Применение операции разыменования к обоим выражениям предполагает, что * * zippo равно * & zippo [0] [0], что сокращается до zippo [0] [0], т.е. значения типа int. Короче говоря, zippo — это адрес адреса, и для получения обычного значения потребуется двукратное разыменование. Адрес адреса или указатель на указатель представляют собой примеры двойной косвенности.
Очевидно, что увеличение количества измерений массива повышает сложность представления с помощью указателей. На этом этапе большинство людей, изучающих С, начинают понимать причины, по которым указатели считаются одним из наиболее трудных аспектов языка. Возможно, вам потребуется еще раз почитать о свойствах указателей, которые описаны выше, после чего обратиться к листингу 10.15, где отображаются значения некоторых адресов и содержимое массивов.
Листинг 10.15. Программа zippo1.c

Массивы и указатели 399
Ниже показан вывод, полученный в одной из систем:
zippo = 0x0064fd38, zippo + 1 = 0x0064fd40 zippo[0] = 0x0064fd38, zippo[0] + 1 = 0x0064fd3c *zippo = 0x0064fd38, *zippo + 1 = 0x0064fd3c zippo[0] [0] =2 * zippo[0] =2 * * zippo = 2 zippo[1][2] = 3 *(*(zippo+1) + 2) = 3
В других системах могут отображаться другие значения адресов и в отличающихся форматах, но взаимосвязи будут такими же, как описано в настоящем разделе. Вывод показывает, что адреса двумерного массива zippo и одномерного массива zippo [0] совпадают. Каждый из них является адресом первого элемента соответствующего массива, и в числовом эквиваленте имеет то же значение, что и & zippo [0] [0].
Однако имеется и различие. В нашей системе тип int занимает 4 байта. Как обсуждалось ранее, zippo [0] указывает на 4-байтовый объект данных. Добавление 1 к zippo [0] должно дать значение, превышающее исходное на 4, что и было получено. (В шестнадцатеричной записи 38 + 4 равно Зс.) Имя zippo — это адрес массива из двух значений int, поэтому он идентифицирует 8-байтовый объект данных. Таким образом, добавление 1 к zippo должно привести к адресу, который на 8 байтов больше исходного, что и происходит на самом деле. (В шестнадцатеричной записи 4 0 на 8 больше, чем 38.)
Читать дальше