Указатель (pointer) — тип данных, хранящий адрес другого объекта в памяти.
Если T — некоторый тип, то T* — указатель на T. Поэтому указатели — не
один тип, а целое семейство: int*, float*, const char**, double***.
Зачем нужно
Память в C++ — нумерованная последовательность байт, номер байта — его адрес. Указатель позволяет обратиться к объекту не по имени, а по адресу: передать объект куда-то, не копируя его, дотянуться до объекта, у которого имени нет вовсе, — например, созданного в динамической памяти. С указателями работают и массивы, см. Арифметика указателей.
Как работает
Взятие адреса
Адрес объекта даёт унарная операция &. Её результат — указатель на тип
объекта:
int x;
const float y = 0;
std::cout << &x << '\n'; // адрес в 16-ричном виде
&x; // int*
&y; // const float*Брать адрес можно только у lvalue — возможность взять адрес и служит критерием lvalue, см. Категории значения:
&x, &(x = 8), &(++x); // можно
&5, &(x + 8), &(x++); // нельзяРазыменование
Унарная * применяется к указателю и возвращает lvalue того объекта, на
который он указывает. Через него объект читают и меняют:
int x = 1;
float y = 2.5;
int* px = &x;
float* py = &y;
*px = 11;
*py = 3.5;
std::cout << x << ' ' << y; // 11 3.5Тип указателя должен соответствовать типу объекта: int* py = &y при
float y не компилируется.
Звёздочка в объявлении (int* px) — часть типа, звёздочка в выражении (*px)
— операция. Это разные вещи с одинаковым знаком.
Указатели и const
Константным бывает объект, на который указывают, и сам указатель:
int x = 0;
int y = 0;
const int* pc = &x; // указатель на константу: *pc менять нельзя
int* const cp = &x; // константный указатель: cp менять нельзя
const int* const cc = &x; // и то и другое
std::cin >> *pc; // CE
pc = &y; // OK
*cp = 5; // OK
cp = &y; // CE
++cp; // CEconst int* и int const* — одно и то же. Читайте тип справа налево:
int const* const — константный указатель на константу int.
Указатель на константу можно направить и на неконстантный объект — тогда
объект просто нельзя изменить через этот указатель. Обратное запрещено:
float* py = &y при const float y — ошибка, иначе через py можно было бы
изменить константу. См. const и constexpr.
Указатель на указатель
Указатель — тоже тип, на него можно сделать указатель:
float x;
float* px = &x;
float** ppx = &px;
float*** pppx = &ppx;
const float* const** const p = ...;
// константный указатель на указатель на константный указатель на константу floatНулевой указатель
Неинициализированный указатель читать нельзя — это
Неопределённое поведение, как и для любой переменной. Присвоить указателю
произвольное число тоже нельзя: int* r = 10; — ошибка. Нужен способ сказать
«указатель никуда не указывает».
Исторически для этого годился литерал 0: по стандарту по нулевому адресу
ничего не лежит. Современный способ — литерал nullptr типа
std::nullptr_t, неявно приводимый к нулевому указателю любого типа:
int* p = nullptr;
const float* pp = nullptr;Правило: нулевые указатели обозначаются только nullptr. Разыменовать
нулевой указатель — UB; удалить через delete — можно, это ничего не делает.
Указатель на void
void* хранит адрес без информации о типе. Напечатать такой адрес можно,
разыменовать — нельзя: неизвестно, что там лежит и сколько байт читать.
int x;
void* p = &x;
std::cout << p; // Ok
std::cout << *p; // CEВернуть тип можно через static_cast: static_cast<int*>(p). Так работают
с результатом malloc, см. Динамическая память.
Тонкости
- Указатели одного типа можно вычитать и сдвигать на целое число — см. Арифметика указателей. Остальные арифметические операции к ним неприменимы.
- Указатель может пережить объект, на который указывает, — тогда он становится висячим.
- Исключение «0 можно присвоить указателю» касается только литерала 0.
int z = 0; int* p = z;не компилируется.
Не из лекции
Последний пункт проверен на GCC 16.2 (
-std=c++20):invalid conversion from 'int' to 'int*'.
Частые ошибки
- Объявить указатель без инициализации и прочитать его.
- Перепутать
const int*(нельзя менять объект) иint* const(нельзя менять указатель). - Писать
0вместоnullptr. - Сохранить адрес локальной переменной и пользоваться им после выхода из блока.