Указатели

Что такое указатели

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

Указатели представляют собой объекты, значением которых служат адреса других объектов (переменных, констант, указателей) или функций. Как и ссылки, указатели применяются для косвенного доступа к объекту. Однако в отличие от ссылок указатели обладают большими возможностями.

Определение указателя

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

тип_данных* название_указателя;

Сначала идет тип данных, на который указывает указатель, и символ звездочки *. Затем имя указателя.

Например, определим указатель на объект типа int:

int* p;

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

#include <iostream>

int main()
{
    int* p;
    std::cout << p << std::endl;
}

Например, в моем случае консоль вывела "0x8" - некоторый адрес в шестнадцатеричном формате (обычно для представления адресов в памяти применяется шестнадцатеричная форма). Но также можно инициализировать указатель некоторым значением:

int* p{};

Поскольку конкрентное значение не указано, указатель в качестве значения получает число 0. Это значение представляет специальный адрес, который не указывает не на что. Также можно явным образом инициализировать нулем, например, используя специальную константу nullptr:

int* p{nullptr};

Хотя никто не запрещает не инициализировать указатели. Однако в общем случае рекомендуется все таки инициализировать, либо каким-то конкретным значением, либо нулем, как выше. Так, к примеру, нулевое значение в будущем позволит определить, что указатель не указывает ни на какой объект.

Cтоит отметить что положение звездочки не влияет на определение указателя: ее можно помещать ближе к типу данных, либо к имени переменной - оба определения будут равноценны:

int* p1{};
int *p2{};

Также стоит отметить, что размер значения указателя (хранимый адрес) не зависит от типа указателя. Он зависит от конкретной платформы. На 32-разрядных платформах размер адресов равен 4 байтам, а на 64-разрядных - 8 байтам. Например:

#include <iostream>

int main()
{
    int *pint{};
    double *pdouble{};
    std::cout << "*pint size: " << sizeof(pint) << std::endl;
    std::cout << "*pdouble size: " << sizeof(pdouble) << std::endl;
}

В данном случае определены два указателя на разные типы - int и double. Переменные этих типов имеют разные размеры - 4 и 8 байт соответственно. Но размеры значений указателей будут одинаковы. В моем случае на 64-разрядной платформе размер обоих указателей равен 8 байтам.

Получение адреса и оператор &

С помощью операция & можно получить адрес некоторого объекта, например, адрес переменной. Затем этот адрес можно присвоить указателю::

int number {25};
int *pnumber {&number}; // указатель pnumber хранит адрес переменной number

Выражение &number возвращает адрес переменной number. Поэтому переменная pnumber будет хранить адрес переменной number. Что важно, переменная number имеет тип int, и указатель, который указывает на ее адрес, тоже имеет тип int. То есть должно быть соответствие по типу. Однако также можно использовать ключевое слово auto:

int number {25};
auto *pnumber {&number}; // указатель pnumber хранит адрес переменной number

Если мы попробуем вывести адрес переменной на консоль, то увидим, что он представляет шестнадцатиричное значение:

#include <iostream>

int main()
{
    int number {25};
    int *pnumber {&number}; // указатель pnumber хранит адрес переменной number
    std::cout << "number addr: " << pnumber << std::endl;
}

Консольный вывод программы в моем случае:

number addr: 0x1543bffc74

В каждом отдельном случае адрес может отличаться и при разных запусках программы может меняться. К примеру, в моем случае машинный адрес переменной number - 0x1543bffc74. То есть в памяти компьютера есть адрес 0x1543bffc74, по которому располагается переменная number. Так как переменная x представляет тип int, то на большинстве архитектур она будет занимать следующие 4 байта (на конкретных архитектурах размер памяти для типа int может отличаться). Таким образом, переменная типа int последовательно займет ячейки памяти с адресами 0x1543bffc74, 0x1543bffc75, 0x1543bffc76, 0x1543bffc77.

Указатели в C++

И указатель pnumber будет ссылаться на адрес, по которому располагается переменная number, то есть на адрес 0x1543bffc74.

Итак, указатель pnumber хранит адрес переменной number, а где хранится сам указатель pnumber? Чтобы узнать это, мы также можем применить к переменной pnumber операцию &:

#include <iostream>

int main()
{
    int number {25};
    int *pnumber {&number}; // указатель pnumber хранит адрес переменной number
    std::cout << "number addr: " << pnumber << std::endl;
    std::cout << "pnumber addr: " << &pnumber << std::endl;
}

Консольный вывод программы в моем случае:

number addr: 0xe1f99ff7cc
pnumber addr: 0xe1f99ff7c0

Здесь мы видим, что переменная number располагается по адресу 0xe1f99ff7cc, а указатель, который хранит этот адрес, - по адресу 0xe1f99ff7c0. Из вывода видно, что обе переменные хранятся совсем рядом в памяти

Получение значения по адресу

Но так как указатель хранит адрес, то мы можем по этому адресу получить хранящееся там значение, то есть значение переменной number. Для этого применяется операция * или операция разыменования ("indirection operator" / "dereference operator"). Результатом этой операции всегда является объект, на который указывает указатель. Применим данную операцию и получим значение переменной number:

#include <iostream>

int main()
{
	int number {25};
    int *pnumber {&number};
	std::cout << "Address = " << pnumber<< std::endl;
	std::cout << "Value = " << *pnumber << std::endl;
}

Пример консольного вывода:

Address = 0x44305ffd4c
Value = 25

Значение, которое получено в результате операции разыменования, можно присвоить другой переменной:

int n1 {25};
int *pn1 {&n1}; // указатель pn1 хранит адрес переменной n1
int n2 { *pn1}; // n2 получает значение, которое хранится по адресу в pn1
std::cout << "n2 = " << n2 << std::endl;	// n2=25

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

int x = 10;
int *px = &x;
*px = 45;
std::cout << "x = " << x << std::endl;	 // 45

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

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850