Указатели сами по себе представляют значения, которые можно хранить в массивах. То есть в итоге получится массив указателей.
Массив указателей определяется одним из трех способов:
тип *имя_массива [размер]; тип *имя_массива [] = инициализатор; тип *имя_массива [размер] = инициализатор;
Используем все эти способы:
int array[] = {1, 2, 3, 4}; int *p1[3]; int *p2[] = { &array[1], &array[2], &array[0] }; int *p3[3] = { &array[3], &array[1], &array[2] };
Массив указателей p1
состоит из трех элементов, но он не инициализирован и является пустым.
Массивы p2
и p3
в качестве элементов хранят адреса на элементы массива a.
Выведем на конслоль значения, на которые ссылаются указатели:
#include <stdio.h> int main(void) { int array[] = {1, 2, 3, 4}; int *p[] = { &array[1], &array[2], &array[0] }; for(int i = 0; i < 3; i++) { printf("%d", *p[i]); } return 0; }
Здесь выражение *p[i]
означает, что мы сначала обращаемся к i-тому адресу в массиве p, а потом применяет операцию разыменования для получения данных по этому адресу.
В итоге на консоль будет выведено в строку:
231
Вместо *p[i]
мы могли бы написать **(p+i)
:
p+i
- к адресу в указателе p
прибавляем число i
и таким образом перемещаемся по указателям в массиве p.
*(p+i)
- разыменовываем i-тый указатель в массиве и в результате получаем адрес одного из элементов из массива array
.
**(p+i)
- получаем значение по полученному на предыдущем шаге адресу элемента из массива array.
#include <stdio.h> int main(void) { int array[] = {1, 2, 3, 4}; int *p[] = { &array[1], &array[2], &array[0] }; for(int i = 0; i < 3; i++) { printf("%d", **(p+i)); } return 0; }
Соответственно если указатель типа char можно представить в виде строки, то массив указателей типа char представляет собой массив строк:
#include <stdio.h> int main(void) { char *fruit[] = {"apricot", "apple", "banana", "lemon", "orange"}; for(int i=0; i < 5; i++) { printf("%s \n", fruit[i]); } return 0; }
Здесь массив указателей fruit
хранит пять строк - фактически пять адресов, по которым размещены начальные символы каждой строки. Результат работы программы:
apricot apple banana lemon orange
Также мы могли бы написать:
#include <stdio.h> int main(void) { char *fruit[] = {"apricot", "apple", "banana", "lemon", "orange"}; for(int i=0; i < 5; i++) { printf("%s \n", *(fruit + i)); } return 0; }
Кроме обычных указателей в языке Си мы можем создавать указатели на другие указатели. Если указатель хранит адрес переменной, то указатель на указатель хранит адрес указателя, на который он указывает. Такие ситуации еще называются многоуровневой адресацией.
Например:
int **ptr;
Переменная ptr представляет указатель на указатель на объект типа int. Две звездочки в определении указателя говорят о том, что мы имеем дело с двухуровневой адресацией. Например:
#include <stdio.h> int main(void) { int x = 22; int *px = &x; // указатель px хранит адрес переменной x int **ppx = &px; // указатель ppx хранит адрес указателя px printf("Address of px: %p \n", (void *)ppx); printf("Address of x: %p \n", (void *)*ppx); printf("Value of x: %d \n", **ppx); return 0; }
Здесь указатель ppx
хранит адрес указателя px
. Поэтому через выражение *ppx
можно получить значение,
которое хранится в указателе px
- адрес переменной x
. А через выражение **ppx
можно получить значение по адресу из px
,
то есть значение переменной x
.