Практический курс по C/C++: https://stepik.org/course/193691
На этом занятии посмотрим, как указатели можно использовать совместно с массивами. Давайте предположим, что в программе
объявляется следующий массив с именем ar:
#include <stdio.h>
int main(void)
{
short ar[] = {4, 3, 2, 1, 5, 6, 7};
return 0;
}
Так вот, имя
массива ar в языке Си
является указателем на массив и содержит адрес первой ячейки, начиная с которой
хранятся элементы этого массива:
А раз это так,
то значение первого элемента можно получить с помощью операции разыменования:
short a_1 = *ar;
printf("a_1 = %d\n", a_1);
И,
действительно, после запуска программы увидим на экране значение 4 первого
элемента. Мало того, используя адресную арифметику, мы можем переходить к
произвольному элементу и работать с ним. Например:
short a_4 = *(ar+3);
printf("a_4 = %d\n", a_4);
В результате,
переменная a_4 будет
содержать значение 1 четвертого элемента. Наконец, мы можем через цикл вывести
все значения элементов этого массива следующим образом:
for(int i = 0; i < sizeof(ar) / sizeof(*ar); ++i)
printf("%d ", *(ar+i));
Давайте
внимательнее посмотрим на эту конструкцию. В цикле for вычисляется
длина массива (число его элементов) с помощью операции sizeof. Вначале
определяется общее число байт, занимаемое массивом sizeof(ar), а затем, оно
делится на число байт, отведенное под один элемент sizeof(*ar). Получаем
общее число элементов в массиве. Однако если ar – это указатель
на массив, то почему операция sizeof(ar) возвращает
количество байт массива, а не указателя? В действительности, здесь ar является именно
указателем на массив, а не просто указателем на ячейки памяти. В языке Си хоть
и отсутствует массив, как структура данных, но указатель такого вида все же
присутствует. Он формируется автоматически, как только мы объявляем какой-либо
массив в программе. Его имя становится указателем на массив.
Чем же
отличается указатель на массив от обычного указателя? В целом, только двумя моментами:
-
операция
sizeof для указателя
на массив возвращает число байт, занимаемых массивом в памяти устройства;
-
адрес
указателя на массив формируется в момент его объявления (размещения в памяти
устройства) и остается неизменным на протяжении работы программы.
Давайте я на
конкретных примерах продемонстрирую эти особенности. Итак, пусть у нас имеется
массив из целых чисел и указатель того же типа:
#include <stdio.h>
int main(void)
{
short ar[] = {4, 3, 2, 1, 5, 6, 7};
short *ptr_ar;
return 0;
}
Так как ar – это
указатель, то мы можем присвоить указателю ptr_ar адрес массива ar следующим
образом:
или так:
Но первый
вариант лучше и визуально и требует меньшего числа операций.
Далее проверим,
можно ли работать с массивом ar через указатель ptr_ar:
short x = *ptr_ar;
*(ptr_ar+1) = -3;
Действительно,
при отладке видим, что переменная x содержит
значение 4, а массив ar изменил второй
элемент на -3.
Теперь проверим,
как будет работать операция sizeof с указателями ar и ptr_ar:
size_t len_1 = sizeof(ar); // len_1 = 14
size_t len_2 = sizeof(ptr_ar); // len_2 = 4
После запуска в
режиме отладки видим значения переменных len_1 = 14 и len_2 = 4. То есть,
эти указатели все же немного различаются. Указатель на массив выдает общее
число байт, отведенных под массив, а ptr_ar – размер
указателя в памяти устройства (4 байта для 32-х разрядных систем).
Попробуем теперь
изменить адрес указателя на массив:
При компиляции
увидим сообщение об ошибке. Так делать нельзя. И, вообще, менять адрес
указателя на массив запрещено. А вот с обычным указателем такая операция вполне
допустима:
Никаких ошибок и
программа успешно выполняется. Мало того, мы с помощью указателя ptr_ar можем
обращаться к произвольным элементам массива, используя оператор квадратные
скобки. Например:
short a_3 = ptr_ar[2]; // a_3 = 2 (при ptr_ar = ar)
На самом деле,
оператор [] – это обычная синтаксическая конструкция, подменяющая собой
адресную арифметику вида:
ar[indx] = *(ar+indx)
То есть,
операция индексирования – это не операция над массивами, а над адресами и не
более того. Мало того, в общем виде ее можно представить так:
a[b] = *(a+b)
При этом
совершенно не важно, что здесь выступает в роли a, а что в роли b. То есть,
формально, обратиться к элементу массива ar с индексом 2
можно и так:
short a_3 = 2[ar]; // *(2+ar)
так как это
эквивалентно операции *(2+ar), а она вполне допустима. Конечно, в
реальных проектах так использовать операцию индексирования не нужно. Да и,
вообще, никогда. Помните, текст программы пишется для человека, а потому не
стоит прописывать различные корявые конструкции, дабы показать глубину своих
познаний. Рабочий коллектив такое не оценит.
Практический курс по C/C++: https://stepik.org/course/193691