Стрелка к стрелке (Двойная стрелка) в C

Привет, начинающие программисты! Сегодня мы отправляемся в захватывающее путешествие в мир указателей, именно в мир указателей к указателям. Я знаю, что у вас может возникнуть такое подозрение: "Указатели? Двойные указатели? Это звучит как кошмар!" Но не волнуйтесь, я обещаю сделать это как можно более интересным и понятным. Так что возьмите свой любимый напиток, устройтесь комфортно, и давайте погружимся!

C - Pointer to Pointer

Что такое двойной указатель в C?

Представьте себе, что вы находитесь на охоте за сокровищами. У вас есть карта (позовем ее указателем), которая ведет к сундуку. Но сюрприз! Внутри сундука есть еще одна карта (еще один указатель), которая ведет к настоящему сокровищу. Вот исключительно что такое двойной указатель — указатель, который указывает на другой указатель.

В программировании на C двойной указатель — это именно то, что вы бы подумали — указатель к указателю. Это переменная, которая хранит адрес другого указателя. Это может показаться немного запутывающе в начале, но не волнуйтесь, мы разберем это шаг за шагом.

Объявление указателя к указателю

Начнем с того, как мы объявляем двойной указатель. Синтаксис довольно простой:

int **ptr;

Здесь ptr — это указатель к указателю к целому числу. Первая звездочка * делает его указателем, а вторая * делает его указателем к указателю.

Пример указателя к указателю (двойной указатель)

Давайте рассмотрим простой пример, чтобы лучше понять это:

#include <stdio.h>

int main() {
int x = 5;
int *p = &x;
int **pp = &p;

printf("Значение x: %d\n", x);
printf("Значение x с использованием p: %d\n", *p);
printf("Значение x с использованием pp: %d\n", **pp);

return 0;
}

Вывод:

Значение x: 5
Значение x с использованием p: 5
Значение x с использованием pp: 5

Разберем это:

  1. Мы объявляем целое число x и инициализируем его значением 5.
  2. Создаем указатель p, который указывает на x.
  3. Создаем двойной указатель pp, который указывает на p.
  4. Затем выводим значение x тремя разными способами:
  • Прямо с использованием x
  • С использованием одиночного указателя p (мы dereferencем его один раз с *p)
  • С использованием двойного указателя pp (мы dereferencем его дважды с **pp)

Все три метода дадут нам одно и то же значение: 5. Это как достичь сокровища с использованием разных карт!

Как работает обычный указатель в C?

Прежде чем мы погружаемся更深 в двойные указатели, давайте быстро рассмотрим, как работают обычные указатели:

int y = 10;
int *q = &y;

printf("Значение y: %d\n", y);
printf("Адрес y: %p\n", (void*)&y);
printf("Значение q: %p\n", (void*)q);
printf("Значение, на которое указывает q: %d\n", *q);

Вывод:

Значение y: 10
Адрес y: 0x7ffd5e8e9f44
Значение q: 0x7ffd5e8e9f44
Значение, на которое указывает q: 10

Здесь q — это указатель, который хранит адрес y. Когда мы используем *q, мы доступаемся к значению, хранящемуся по этому адресу.

Как работает двойной указатель?

Теперь распространим это на двойные указатели:

int z = 15;
int *r = &z;
int **rr = &r;

printf("Значение z: %d\n", z);
printf("Адрес z: %p\n", (void*)&z);
printf("Значение r: %p\n", (void*)r);
printf("Адрес r: %p\n", (void*)&r);
printf("Значение rr: %p\n", (void*)rr);
printf("Значение, на которое указывает r: %d\n", *r);
printf("Значение, на которое указывает rr: %p\n", (void*)*rr);
printf("Значение, на которое указывает значение, на которое указывает rr: %d\n", **rr);

Вывод:

Значение z: 15
Адрес z: 0x7ffd5e8e9f48
Значение r: 0x7ffd5e8e9f48
Адрес r: 0x7ffd5e8e9f50
Значение rr: 0x7ffd5e8e9f50
Значение, на которое указывает r: 15
Значение, на которое указывает rr: 0x7ffd5e8e9f48
Значение, на которое указывает значение, на которое указывает rr: 15

Это может показаться немного перегружающе, но разберем это:

  1. z — это целое число с значением 15.
  2. r — это указатель, который хранит адрес z.
  3. rr — это двойной указатель, который хранит адрес r.
  4. *r дает нам значение z (15).
  5. *rr дает нам значение r (которое является адресом z).
  6. **rr дает нам значение z (15).

Представьте себе так: rr указывает на r, который указывает на z. Так что **rr — это как сказать "следуйте первому указателю, затем следуйте второму указателю и дайте мне значение там".

Двойной указатель ведет себя так же, как обычный указатель

Вот небольшое секрет: двойной указатель — это просто указатель, но вместо того чтобы указывать на int или float, он указывает на другой указатель. Это означает, что мы можем делать с двойными указателями все те же вещи, что и с обычными указателями.

Например, мы можем использовать двойные указатели с массивами:

int main() {
char *fruits[] = {"Apple", "Banana", "Cherry"};
char **ptr = fruits;

for(int i = 0; i < 3; i++) {
printf("%s\n", *ptr);
ptr++;
}

return 0;
}

Вывод:

Apple
Banana
Cherry

В этом примере fruits — это массив указателей (каждый из которых указывает на строку), а ptr — это указатель к указателю к символу (который может указывать на элементы fruits).

Многоуровневые указатели в C (Возможен ли тройной указатель?)

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

Вот пример тройного указателя:

int x = 5;
int *p = &x;
int **pp = &p;
int ***ppp = &pp;

printf("Значение x: %d\n", ***ppp);

Вывод:

Значение x: 5

Но помните, что просто потому, что вы можете, не значит, что вы должны. Многоуровневые отсылки могут сделать код сложнее для чтения и поддержки. Как говорится в старой программистской пословице, "Все проблемы в компьютерной науке можно решить добавлением еще одного уровня отсылки... кроме проблемы слишком многих уровней отсылки!"

Заключение

Поздравляем! Вы только что перенеслись по морскому дну двойных указателей в C. Помните, что как многие концепции в программировании, указатели к указателям могут показаться запутывающими в начале, но с практикой они станут второй натурой.

Вот таблица, подводящая ключевые моменты, которые мы обсудили:

Концепция Синтаксис Описание
Обычный Указатель int *p; Указывает на целое число
Двойной Указатель int **pp; Указывает на указатель к целому числу
Dereferencing *p Доступ к значению, на которое указывает p
Двойной Dereferencing **pp Доступ к значению, на которое указывает указатель, на который указывает pp
Оператор адреса &x Получение адреса x

Продолжайте практиковаться, будьте любознательными, и помните — каждый эксперт когда-то начинал с нуля. Счастливого кодирования!

Credits: Image by storyset