Массивы указателей, строки и многоуровневая адресация

Последнее обновление: 05.01.2023

Массивы указателей

Указатели сами по себе представляют значения, которые можно хранить в массивах. То есть в итоге получится массив указателей.

Массив указателей определяется одним из трех способов:

тип *имя_массива [размер];
тип *имя_массива [] = инициализатор;
тип *имя_массива [размер] = инициализатор;

Используем все эти способы:

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.

двухуровневая адресация и указатель на указатель в языке программирования Си
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850