Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Примером такого замаскированного применения может служить тот факт, что имя массива представляет собой также и адрес его первого элемента. Это означает, что если flizny — массив, то следующее выражение будет истинным:
flizny == &flizny[0]; // имя массива является адресом его первого элемента
И flizny, и &flizny[0] представляют адрес в памяти, где находится первый элемент массива. (Вспомните, что & — операция взятия адреса.) Кроме того, это константы, т.к. они остаются фиксированными на протяжении всего времени действия программы. Тем не менее, их можно присваивать в качестве значений и временной типа указателя, значение которой можно изменять, как показано в листинге 10.8. Посмотрите, что происходит со значением указателя, когда вы прибавляете к нему число. (Как вы, возможно, помните, спецификатор %р для указателей обычно приводит к отображению их шестнадцатеричных значений.)
Листинг 10.8. Программа pnt add. с
382 Глава 10
Вот пример вывода:
Во второй строке выводятся начальные адреса двух массивов, в следующей после нее строке показан результат прибавления к адресу 1 и т.д. Имейте в виду, что адреса представлены в шестнадцатеричной форме, поэтому dd на 1 больше, чем dc, a al на 1 больше, чем аО. Что же мы здесь имеем?
Довольно глупо? Нет — хитро! В нашей системе реализована побайтная адресация, но тип short занимает 2 байта, а тип double — 8 байтов. В таком случае “добавление 1 к указателю” означает добавление одной единицы хранения. Для массивов данный факт означает, что адрес увеличивается до адреса следующего элемента, а не просто до следующего байта (рис. 10.3). Это одна из причин того, почему нужно объявлять вид объекта, на который указывает указатель. Одного лишь адреса недостаточно, т.к. компьютер должен знать, сколько байтов требуется для хранения объекта. (Это справедливо даже для указателей на скалярные переменные; иначе операция *pt, извлекающая значение, не будет корректно работать.)
Рис. 10,3. Массив и добавление к указателю
Теперь мы можем более четко определить, что означают понятия “указатель на int”, “указатель на float” и “указатель на любой другой объект данных”.
• Значение указателя — это адрес объекта, на который он указывает. Внутреннее представление адреса зависит от оборудования. Многие компьютеры, включая IBM PC и Macintosh, адресуемы по байтам, т.е. байты памяти нумеруются последовательно. Адресом большого объекта, такого как переменная типа double, как правило, является адрес первого байта объекта.
Массивы и указатели 383
• Применение операции * к указателю дает значение, хранящееся в объекте, на который ссылается указатель.
• Добавление 1 к указателю увеличивает его значение на размер в байтах типа, на который он указывает.
Мастерство языка С позволяет обеспечивать следующие равенства:
*dates + 2 == &date[2] // тот же адрес
*(dates + 2) == dates[2] // то же значение
Такие отношения подводят итог под тесной связью между массивами и указателями. Это значит, что вы можете использовать указатель для идентификации отдельного элемента массива и для получения его значения. По существу мы имеем две разных формы записи для одного и того же действия. В действительности, стандарт языка С описывает массивы в терминах указателей. То есть стандарт определяет ar[n] как * (ar + n). Второе выражение можно интерпретировать как “перейти к ячейке памяти ar, перемеситься на n единиц и извлечь хранящееся там значение”.
Кстати, не путайте *(dates + 2) с *dates + 2. Операция разыменования (*) имеет более высокий приоритет, чем операция +, так что второе выражение означает (*dates)+2:
*(dates + 2) // значение 3-го элемента массива dates
*dates +2 // добавление 2 к значению 1-го элемента
Наличие такой связи между массивами и указателями позволяет применять при написании программы любой из подходов. Например, программа в листинге 10.9 после компиляции и запуска генерирует тот же вывод, что и программа из листинга 10.1.
Здесь days — это адрес первого элемента массива, индекс days + index — адрес элемента days [index] и * (days + index) — значение этого элемента, в точности как days [index]. Цикл по очереди ссылается на каждый элемент массива и выводит обнаруженное содержимое.
Есть ли какое-то преимущество в написании программы подобным образом? Вообще говоря, нет — для любой формы записи компилятор генерирует один и тот же код. Основная цель кода в листинге 10.9 состояла в том, чтобы показать, что формы записи через массивы и через указатели являются эквивалентными. Этот пример демонстрирует возможность использования формы записи с указателями при работе с массивами. Обратное утверждение также верно; при работе с указателями можно применять форму записи с массивами. Это становится важным, когда имеется функция, принимающая массив в качестве аргумента.
384 глава 10
Функции, массивы и указатели
Предположим, что необходимо написать функцию, которая оперирует на массиве. Например, пусть нужна функция, возвращающая сумму элементов массива. Представим, что marbles — это имя массива значений int. Как будет выглядеть вызов такой функции? Разумно предположить, что он должен иметь следующий вид:
total = sum (marbles); // возможный вызов функции
А каким должен быть прототип этой функции? Вспомните, что имя массива является адресом его первого элемента, так что фактический аргумент marbles, будучи адресом значения int, должен присваиваться формальному параметру, который представляет собой указатель на тип int:
int sumlint * ar); // соответствующий прототип
Какую информацию функция sum() получает из этого аргумента? Она получает адрес первого элемента массива и узнает, что в этой ячейке она найдет значение int. Обратите внимание, что данная информация ничего не говорит о количестве элементов в массиве. Мы поставлены перед выбором одного из двух вариантов получения этой информации функцией. Первый вариант предусматривает кодирование внутри функции фиксированного размера массива:
int sumlint * ar) // соответствующее определение
{
int i;
int total = 0;
for ( i = 0; i < 10; i++) // предполагается наличие 10 элементов
total += ar[i]; // ar[i] - то же самое, что и *(ar + i)
return total;
}
Здесь используется тот факт, что аналогично применению указателей с именами массивов, форму записи массивов можно использовать с указателями. Кроме того, вспомните, что операция += добавляет значение своего правого операнда к левому операнду. Следовательно, total является текущей суммой элементов массива.
Читать дальшеИнтервал:
Закладка: