Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
*(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; // ошибка на этапе компиляции
Такие ограничения распространяются и на более сложные типы. Предположим, что есть следующие объявления:
int * pt; int (*ра) [3]; int ar1[2] [3]; int ar2[3] [2];
int **р2; // указатель на указатель
Взгляните на показанный далее код:
pt = &ar1 [0][0]; // оба - указатели на int
pt = &ar1 [0]; // оба - указатели на int
pt = arl; // недопустимо
pa = arl; // оба - указатели на int[3]
// недопустимо // оба - указатели на int // оба - указатели на int // недопустимо
Обратите внимание, что во всех недопустимых случаях присваивания вовлечены два указателя, которые не указывают на один и тот же тип. Например, pt указывает на одиночное значение int, но arl — на массив из трех значений int. Аналогично, ра указывает на массив из двух значений int, следовательно, он совместим с arl, но не с ar2, который указывает на массив из двух значений int.
Два последних примера немного запутаны. Переменная р2 представляет собой указатель на указатель на тип int, в то время как ar2 — это указатель на массив из двух значений int (или, выражаясь короче, указатель на массив int [2]). Таким образом, р2 и ar2 — разные типы, и вы не можете присвоить ar2 указателю р2. Но *р2 имеет тип указателя на int, что обеспечивает его совместимость с ar2 [0]. Вспомните, что ar2 [0] является указателем на свой первый элемент, ar2 [0] [0], что делает ar2 [0] также и типом указателя на int.
В целом, многократные операции разыменования сложны. Например, рассмотрим следующий фрагмент кода:
int х = 20; const int у = 23; int * p1 = &х; const int * р2 = &у; const int ** рр2;
p1 = р2; // небезопасно — присваивание константного значения неконстантному
р2 = p1; // допустимо -- присваивание константного значения константному рр2 = &р1; // небезопасно -- присваивание вложенных типов указателей
Как вы видели ранее, присваивание указателя const указателю, отличному от const, не является безопасным, т.к. новый указатель мог бы применяться для изменения данных типа const. Хотя код и скомпилируется, возможно, с выдачей предупреждения, результат его выполнения не определен. Но присваивание указателя не const указателю const допустимо при условии, что вы имеете дело только с одним уровнем косвенности:
р2 = p1; // допустимо -- присваивание неконстантного значения константному
Тем не менее, такие присваивания перестают быть безопасными, когда вы переходите к двум уровням косвенности. Например, вы могли бы написать такой код:
const int **рр2; int *р1;
const int n = 13;
рр2 = &р1; // разрешено, но квалификатор const игнорируется
*рр2 = &n; // допустимо, оба const, но p1 устанавливается указывающим на п
*р1 =10; // допустимо, но производится попытка изменить константу п
Что происходит? Как упоминалось ранее, в стандарте говорится, что результат изменения константных данных с использованием указателя, отличного от const, не определен. Например, компиляция короткой программы с этим кодом с помощью gcc в среде Terminal (интерфейс OS X для доступа к лежащей в основе системе Unix) приводит к тому, что n получает значение 13, но применение компилятора clang в той же среде обеспечивает для n значение 10. При этом оба компилятора предупреждают о несовместимых типах указателей. Разумеется, предупреждения можно игнорировать, но лучше не доверять результатам выполнения этой программы.
Читать дальшеИнтервал:
Закладка: