Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
show_array(rates, 5); // допустимо
show_array(locked, 4); // допустимо
Таким образом, использование ключевого слова const в определении параметра функции не только защищает данные, но также позволяет функции работать с массивами, которые были объявлены как const.
Однако функции вроде mult_array() не должно передаваться имя константного массива в виде аргумента:
mult_array(rates, 5, 1.2); // допустимо
mult_array(locked, 4, 1.2); // не допустимо
В стандарте С говорится о том, что попытка модификации данных const, таких как locked, с применением отличного от const идентификатора, например, формального аргумента ar функции mult_array(), приводит к неопределенному поведению.
Существуют и другие варианты использования const. К примеру, вы можете объявить и инициализировать указатель таким образом, чтобы его нельзя было заставить указывать на что-нибудь другое. Хитрость в том, где размещено ключевое слово const:
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.)
Программа демонстрирует, что zippo [0] и *zippo идентичны, как и должно быть. Затем она показывает, что для получения хранящегося в массиве значения имя двумерного массива должно быть разыменовано дважды. Это может быть сделано за счет двукратного применения операции разыменования (*) или операции квадратных скобок ([]). (Этого также можно достичь с использованием одной операции * и одного набора квадратных скобок, но давайте не будем отвлекаться на исследования всех возможных вариантов.)
В частности, обратите внимание, что эквивалент zippo [2] [1] в форме записи с указателями выглядит как * (* (zippo+2) + 1). Вероятно, хотя бы раз в жизни вам приходилось прикладывать усилия, чтобы разобрать такое выражение. Давайте будем анализировать это выражение пошагово:
zippo <-адрес первого элемента длиной в два значения int
zippo+2 <-адрес третьего элемента длиной в два значения int
Читать дальшеИнтервал:
Закладка: