Адресная арифметика
Рассмотрим следующую программу:
#include <stdio.h>
#define N 5
main () {
int arrI[N], i;
for (i=0; i<N; i++)
printf("%p\n", &arrI[i]);
}
В данной программе создается массив arrI, после чего в цикле for
выводятся адреса памяти
каждого элемента массива. Примерный результат может выглядеть так:
0x7ffffbff4050
0x7ffffbff4054
0x7ffffbff4058
0x7ffffbff405c
0x7ffffbff4060
Обратите внимание, что адрес каждого следующего элемента массива отличается от предыдущего на 4 единицы. В вашей системе эта разница может составлять 2 единицы. Это объясняется тем, что на одно значение типа int в памяти отводится несколько байтов, и элементы массива хранятся последовательно.
Теперь объявим указатель на тип целое число и присвоим ему адрес первого элемента массива:
int *pI;
pI = &arrI[0];
Цикл for
изменим таким образом:
for (i=0; i<N; i++)
printf("%p\n", pI + i);
Здесь к значению pI, которое является адресом, добавляется 0, затем 1, 2, 3 и 4. Можно было бы предположить, что добавление единицы даст нам адрес следующего байта, но это не так.
Тип указателя определяет, сколько байтов занимает значение, на которое он указывает. Хотя pI указывает лишь на один байт, он "знает", что его значение располагается на все четыре (или два). Потому прибавление единицы приводит нас к следующему значению, а не байту. Поэтому приведенный цикл с указателем будет корректно отображать адреса элементов массива.
Когда мы добавляем или вычитаем целые значения из указателей, мы используем так называемую адресную арифметику.
Напишите программу с массивом из десяти вещественных чисел. Присвойте указателю адрес четвертого элемента, затем выведите адреса 4, 5 и 6-го элементов.
Имя массива - это указатель на адрес его первого элемента
Это правда, и следует воспринимать как аксиому. Выполните такое выражение, чтобы убедиться:
printf("%p = %p\n", arrI, &arrI[0]);
Таким образом, имя массива — это ровно указатель. Несмотря на особенность, выражения pI = &arrI[N]
и pI = arrI
дают одинаковый результат: адрес первого элемента массива.
Поскольку имя массива указывает на адрес, мы легко можем получать адреса элементов так:
for (i=0; i<N; i++)
printf("%p\n", arrI + i);
Таким же образом можно получить значения элементов массива:
for (i=0; i<N; i++)
printf("%d\n", *(arrI + i));
Замечание. Если массив объявили как автоматическую переменную (не глобальную и не статическую) без инициализации, то он будет содержать случайные значения.
Таким образом, выражение arrI[3] является более удобной записью для *(arr+3).
Взаимозаменяемость имени массива и указателя
Раз имя массива является указателем, почему бы не применять обычный указатель в стиле обращения к элементам массива?
int arrI[N], i;
int *pI;
pI = arrI;
for (i=0; i<N; i++)
printf("%d\n", pI[i]);
Из этого следует, что если arrI — массив, а pI — указатель на его первый элемент, то следующая пара выражений дает одинаковый результат:
arrI[i]
иpI[i]
;&arrI[i]
и&pI[i]
;arrI+i
иpI+i
;*(arrI+i)
и*(pI+i)
.
Чему равны результаты выполнения указанных пар выражений: адреса или значения элементов массива?
Указателю pI можно присвоить адрес любого элемента массива: pI =&arrI[2]
или pI = arr+2
.
В этом случае результаты пар выражений не совпадут. Например, выражение arrI[i]
вернет i-ый элемент массива, а pI[i]
вернет i-ый элемент, начиная с того, на который указывает pI.
Присвойте указателю (pI) адрес не первого элемента массива (arrI). В одном цикле выводите результаты выражений arrI[i]
и pI[i]
,
где на каждой итерации i одинаково. Объясните полученный результат.
Имя массива — это указатель-константа
Несмотря на взаимозаменяемость имени массива и указателя, между ними есть различия. Указатель может быть сдвинут, его значение можно изменять. Имя массива всегда указывает на первый элемент массива и его значение неизменно.
Допустимо выражение pI = arrI
, но arrI = pI
недопустимо.
Имя массива является константой, что не отменяет изменения значений элементов. Отличие обычной переменной в том, что ее адрес не изменяется в
процессе выполнения программы, изменяются лишь ее значения. Тем самым, имя массива — тоже переменная, только содержит адрес.
Следует, что операции присваивания, инкрементирования и декрементирования допустимы для указателей, но запрещены для имени массива.
Изучите программу ниже. Каков ее функционал? Почему она работает именно так? Проверьте свои догадки, запустив программу.
#include <stdio.h>
main () {
char str[20], *ps = str, n=0;
printf("Enter word: ");
scanf("%s", str);
while(*ps++ != '\0') n++;
printf("%d\n", n);
}