Программа демонстрирует, что zippo [0] и *zippo идентичны, как и должно быть. Затем она показывает, что для получения хранящегося в массиве значения имя двумерного массива должно быть разыменовано дважды. Это может быть сделано за счет двукратного применения операции разыменования (*) или операции квадратных скобок ([]). (Этого также можно достичь с использованием одной операции * и одного набора квадратных скобок, но давайте не будем отвлекаться на исследования всех возможных вариантов.)
В частности, обратите внимание, что эквивалент zippo [2] [1] в форме записи с указателями выглядит как * (* (zippo+2) + 1). Вероятно, хотя бы раз в жизни вам приходилось прикладывать усилия, чтобы разобрать такое выражение. Давайте будем анализировать это выражение пошагово:
zippo <-адрес первого элемента длиной в два значения int
zippo+2 <-адрес третьего элемента длиной в два значения int
*(zippo+2) <-третий элемент, представляющий собой массив из двух int,
следовательно, это адрес его первого элемента, т.е. значения int *(zippo+2) + 1 <-адрес второго элемента в массиве из двух int, также значение int *(*(zippo+2) +1)<-значение второго int в третьей строке (zippo[2] [1] )
Смысл этой причудливой формы с указателями заключается вовсе не в том, что ее можно применять вместо более простой записи zippo [2] [1]. Смысл в том, что при наличии указателя на двумерный массив и необходимости извлечь значение можно использовать более прос тую форму записи в виде массива, а не форму с указателями.
На рис 10.5 показано еще одно представление отношений между адресами массива, содержимым массива и указателями.
400 Глава 10

Рис. 10.5. Массив из массивов
Указатели на многомерные массивы
Как бы вы объявили переменную pz типа указателя, которая может указывать на двумерный массив, такой как zippo? Указатель подобного рода мог бы применяться, например, при написании функции, которая имеет дело с массивами вроде zippo. Достаточно ли будет типа указателя на int? Нет. Такой тип совместим с zippo [0], который указывает на одиночное значение int. Но zippo — это адрес его первого элемента, который сам является массивом из двух значений int. Отсюда следует, что pz должен указывать на массив с двумя элементами int, а не на одиночное значение int. Вот как можно поступить:
int (* pz)[2]; // pz указывает на массив из 2 значений int
Приведенный оператор определяет, что pz представляет собой указатель на массив из двух значений типа int. Для чего здесь нужны круглые скобки? Дело в том, что скобки[] имеют более высокий приоритет, чем *. Это значит, что в объявлении вида
int * рах [2]; // рах - массив из двух указателей на int
сначала используются квадратные скобки, делая рах массивом с какими-то двумя элементами. Затем применяется операция *, превращая рах в массив из двух указателей. Наконец, использование int делает рах массивом из двух указателей на int. Приведенное объявление создает Дед указателя на одиночные значения int, но в пер воначальной версии круглые скобки обеспечивают применение операции * первой, создавая один указатель на массив из двух значений int. В листинге 10.16 показано, что такой указатель можно использовать подобно исходному массиву.
Листинг 10.16. Программа zippo2.c

Массивы и указатели 401

Вот новый вывод:
pz = 0x0064fd38, pz + 1 = 0x0064fd40 pz[0] = 0x0064fd38, pz [0] + 1 = 0x0064fd3c *pz = 0x0064fd38, *pz + 1 = 0x0064fd3c pz[0][0] = 2 *pz[0] = 2 **pz = 2 pz [2] [1] = 3 *(*(pz+2) + 1) = 3
И снова в своей системе вы можете получить другие адреса, но взаимосвязи останутся такими же. Как и было обещано, форму записи наподобие pz [2] [1] можно применять, даже если pz является указателем, а не именем массива. Говоря в общем, вы можете представлять отдельные элементы, используя форму записи с участием массива или указателей либо с именем массива, либо с указателем:
zippofm][n] == *(*(zippo + m) + n) pz [m] [n] == * (* (pz + m) + n)
Совместимость указателей
Правила прнсванвання одного указателя другому строже таких правил для числовых типов. Например, вы можете присвоить значение int nеременной double, не используя преобразование типа, но нельзя сделать то же самое для указателей на эти два типа:
int n = 5; double х; int * pl = &n; double * pd = &х;
х = n; // неявное преобразование типа
pd = pl; // ошибка на этапе компиляции
Такие ограничения распространяются и на более сложные типы. Предположим, что есть следующие объявления:
Читать дальше