В C указатель на тип T
указывает на место, где хранятся некоторые данные типа T
.Чтобы конкретизировать, я расскажу о T = int
ниже.
Самым простым использованием указателя может быть указание на одно значение:
int a = 42;
int *pa = &a;
Now, *pa
иa
одинаковы и равны 42
.Кроме того, *pa
и pa[0]
оба эквивалентны: так, например, вы можете сделать:
*pa += 1; /* a is 43 */
pa[0] += 1; /* a is 44 */
a += 1; /* a is 45 */
Фактически, компилятор C автоматически переводит pa[0]
в *(pa+0)
.
Указатель может указывать на местоположение, которое находится внутри последовательности данных:
int arr[] = { 1, 2, 3 }; /* 3 ints */
int *parr = arr; /* points to 1 */
Теперь наша память выглядит так:
+---+---+---+
arr: | 1 | 2 | 3 |
+---+---+---+
+------+ |
| parr | ----+
+------+
parr
- указательэто указывает на первый элемент arr
на картинке выше.Между прочим, parr
также имеет рамку вокруг него, потому что нам нужно хранить объект parr
где-то в памяти.Значение parr
является адресом первого элемента arr
.
Теперь мы можем использовать parr
для доступа к элементам arr
:
arr[0] == parr[0]; /* true */
parr[1]++; /* make arr[1] equal to 3 */
Таким образом, указатель может использоваться для обозначения: «Я указываю на первый из n элементов в непрерывном хранилище некоторых объектов».Конечно, нужно знать, сколько объектов существует для этой схемы, но как только мы не забудем это сделать, это чрезвычайно удобный способ доступа к памяти в C.
Указатель может быть сделан для указаниядинамически распределенной памяти:
#include <stdlib.h>
size_t n;
/* now, obtain a value in n at runtime */
int *p = malloc(n * sizeof *p);
Если вызов malloc()
завершается успешно, p
теперь указывает на первую из смежных областей, выделенных на 10 int
с.Теперь мы можем использовать в наших программах от p[0]
до p[n-1]
.
Вы, вероятно, знали большинство или все вышеперечисленное :-), но все же вышеизложенное помогает понять, что я скажу дальше.
Помните, мы говорили, что указатель может указывать на непрерывную последовательность объектов одного типа?«Тот же тип» может быть и другим типом указателя.
#include <stdlib.h>
int **pp;
pp = malloc(3 * sizeof *pp);
Теперь pp
указывает на int *
.Возвращаясь к нашей более ранней картинке:
+------+------+------+
| | | |
+------+------+------+
+------+ |
| pp | ----+
+------+
И каждый из 3 блоков - это int *
, который может указывать на первый элемент непрерывной последовательности int
s:
for (i=0; i < 3; ++i)
pp[i] = malloc((i + 1) * sizeof *ppi[i]);
Здесь мы выделили пространство для одного целого в pp[0]
, 2 в pp[1]
и 3 в pp[3]
:
+------+ +---+
pp -------->| |-------->| |
+------+ +---+---+
| |-------->| | |
+------+ +---+---+---+
| |-------->| | | |
+------+ +---+---+---+
Итак, pp[0]
- указатель на один int
, и int
является единственным int
в динамически распределенном блоке int
с.Другими словами, pp[0][0]
является int
и указывает на самое верхнее поле «width-3» выше.Аналогичным образом, pp[1][0]
и pp[1][1]
являются действительными и представляют собой два поля под полем pp[0][0]
.
Наиболее распространенное использование указателя на указатель - это создание двумерного «массива» во время выполнения.:
int **data;
size_t i;
data = malloc(n * sizeof *data);
for (i=0; i < n; ++i)
data[i] = malloc(m * sizeof *data[i]);
Теперь, при условии, что все malloc()
s выполнены успешно, data[0]
... data[n-1]
являются действительными int *
значениями, каждое из которых указывает на отдельную длину m
непрерывно int
objects.
Но, как я показал выше, указатель на указатель не обязательно должен иметь одинаковое количество элементов в каждой из своих «строк».Наиболее очевидный пример - argv
в main()
.
К настоящему времени, как вы можете догадаться, указатель "3-уровневый глубокий", такой как int ***p;
, в порядке, иможет быть полезен в C.