- Объяснять, что такое указатель, получать адрес через
&и разыменовывать через* - Сравнивать указатели со ссылками и проверять
nullptr - Объяснять, как
newиdeleteвыделяют динамическую память и чем они опасны - Применять принцип RAII и
std::unique_ptr
Ты хочешь дать другу книгу, но она очень тяжёлая. Вместо того чтобы нести её, ты пишешь на листке адрес: «улица Низами, 12, третья полка». По адресу друг сам найдёт книгу. В C++ указатель — как раз такой листок: он хранит не само значение, а его адрес в памяти. Указатели дают C++ скорость и гибкость, но невнимательность может дорого обойтись.
Адреса и указатели
Память компьютера состоит из пронумерованных ячеек, и каждая переменная находится по некоторому адресу. Оператор & возвращает адрес переменной. Адрес обычно выводится в шестнадцатеричной системе и может меняться при каждом запуске программы:
#include <iostream>
int main() {
int coins = 50;
std::cout << "Value: " << coins << '\n';
std::cout << "Address: " << &coins << '\n';
return 0;
}0x7ffc5a2b1c4c — на каждом компьютере и при каждом запуске свой.Переменная, которая хранит адрес другой переменной в памяти. Запись int* p означает «p — указатель на значение типа int». Выражение *p обращается к значению по этому адресу — это называется разыменованием.
#include <iostream>
int main() {
int coins = 50;
int* ptr = &coins;
std::cout << "coins = " << coins << '\n';
std::cout << "*ptr = " << *ptr << '\n';
*ptr = 75;
std::cout << "coins = " << coins << '\n';
std::cout << std::boolalpha << (ptr == &coins) << '\n';
return 0;
}coins = 50 *ptr = 50 coins = 75 true
*ptr = 75 меняет значение по адресу указателя, то есть саму переменную coins.| Запись | Значение |
|---|---|
&x | адрес переменной x |
int* p | объявление указателя на int |
*p | значение по адресу из p |
nullptr | указатель, который никуда не указывает |
p->name | краткая запись (*p).name |
Ссылки и nullptr
В прошлых уроках ты познакомился со ссылками (int& ref = a;) — ссылка это второе имя переменной. В отличие от указателя, ссылка обязательно привязывается к переменной при объявлении и потом не может быть перепривязана к другой. Указатель же в любой момент можно направить на другой адрес, а может и вовсе никуда не указывать.
#include <iostream>
int main() {
int a = 10;
int b = 20;
int& ref = a;
int* ptr = &a;
ref += 5;
std::cout << "a = " << a << '\n';
ptr = &b;
*ptr += 5;
std::cout << "b = " << b << '\n';
std::cout << "a = " << a << '\n';
return 0;
}a = 15 b = 25 a = 15
Указателю, который никуда не указывает, присваивают nullptr. Разыменование nullptr (*p) — неопределённое поведение, и обычно программа аварийно завершается. Поэтому, если указатель может быть пустым, проверяй его перед использованием:
#include <iostream>
void printValue(const int* p) {
if (p == nullptr) {
std::cout << "No value\n";
return;
}
std::cout << "Value: " << *p << '\n';
}
int main() {
int level = 3;
printValue(&level);
printValue(nullptr);
return 0;
}Value: 3 No value
Динамическая память: new и delete
Обычные локальные переменные создаются в стеке и автоматически удаляются, когда функция завершается. Но иногда память нужно выделить во время работы программы и нужного размера. Оператор new выделяет место в куче (heap) и возвращает его адрес. Такая память сама не освобождается: её нужно вручную вернуть через delete (а массив — через delete[]).
#include <iostream>
int main() {
int* p = new int(42);
std::cout << *p << '\n';
delete p;
p = nullptr;
int size = 4;
int* squares = new int[size];
for (int i = 0; i < size; i++) {
squares[i] = i * i;
}
std::cout << squares[3] << '\n';
delete[] squares;
return 0;
}42 9
RAII и умные указатели
Современный C++ решает эти проблемы с помощью принципа RAII (Resource Acquisition Is Initialization — «получение ресурса есть инициализация»): объект берёт ресурс во владение и автоматически освобождает его при своём уничтожении. Именно так работают std::vector и std::string. Для одиночного объекта есть умный указатель std::unique_ptr из <memory>: он единственный владелец памяти и сам удаляет её, выходя из области видимости.
#include <iostream>
#include <memory>
#include <utility>
int main() {
std::unique_ptr<int> score = std::make_unique<int>(100);
*score += 50;
std::cout << "Score: " << *score << '\n';
std::unique_ptr<int> other = std::move(score);
std::cout << std::boolalpha << (score == nullptr) << '\n';
std::cout << "Other: " << *other << '\n';
return 0;
} // other deletes the memory automaticallyScore: 150 true Other: 150
unique_ptr нельзя скопировать, владение можно только передать через std::move; после этого score пуст.Главное
- Указатель хранит адрес другой переменной:
int* p = &x;. &даёт адрес, а*обращается к значению по адресу.- Ссылка всегда привязана к одной переменной; указатель можно перенаправить, и он может быть
nullptr. - Память из
newнужно освобождать черезdelete(для массивовdelete[]), иначе будет утечка. - RAII:
std::vector,std::stringиstd::unique_ptrосвобождают память автоматически — отдавай им предпочтение.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
&x?