Указатели в C++: пошаговое объяснение для новичка
За полчаса разберётесь, что хранит указатель, чем он отличается от ссылки и почему разыменование пустого указателя роняет программу. С кодом, который можно скомпилировать и запустить.

К концу статьи вы будете понимать, что физически лежит в переменной-указателе, как получить адрес объекта и значение по адресу, чем указатель отличается от ссылки и почему *p при p == nullptr завершает программу аварийно. Времени уйдёт минут тридцать, если разбирать примеры не глазами, а компилятором. Примеры собираются любым современным компилятором с поддержкой C++17 и новее: g++, clang++ или MSVC.
Указатель — не магия и не «сложная тема из середины учебника». Это переменная, в которой лежит число: адрес ячейки памяти. Всё остальное — операции над этим числом.
Что нужно до начала
- Установленный компилятор: g++ или clang++ (Linux/macOS), либо MSVC / MinGW (Windows). Проверить:
g++ --versionдолжна вывести номер версии. - Любой текстовый редактор или IDE.
- Понимание, что такое обычная переменная и функция. Про массивы и классы знать не обязательно — здесь их почти нет.
Компилировать примеры будем так: g++ -std=c++17 -Wall main.cpp -o main, запускать — ./main. Флаг -Wall включает предупреждения; они здесь друзья, а не шум.
Шаг 1. Адрес переменной
У каждой переменной в памяти есть адрес — номер байта, с которого она начинается. Оператор & перед именем переменной даёт этот адрес.
#include <iostream>
int main() {
int age = 30;
std::cout << "Значение: " << age << "\n";
std::cout << "Адрес: " << &age << "\n";
return 0;
}
Проверка: в консоли появятся две строки. Первая — 30, вторая — что-то вроде 0x7ffee3b8c9ac. Конкретное число будет своё при каждом запуске, и это нормально: операционная система выдаёт память там, где свободно.
Шаг 2. Переменная-указатель
Теперь заведём переменную, которая будет хранить этот адрес. Тип пишется как int* — «указатель на int».
#include <iostream>
int main() {
int age = 30;
int* pAge = &age; // pAge хранит адрес age
std::cout << "pAge хранит адрес: " << pAge << "\n";
std::cout << "адрес самой age: " << &age << "\n";
return 0;
}
Проверка: обе строки выведут одинаковый адрес. Это и есть суть: pAge — не копия числа 30, а «стрелка», указывающая на ту ячейку, где лежит age.
Звёздочка в объявлении int* pAge — часть типа. Тип int* означает «адрес, по которому лежит int». Компилятору важно знать, что именно лежит по адресу, чтобы правильно читать нужное число байт.
Почему тип указателя важен
Указатель int* и указатель double* хранят одинаковые по природе числа-адреса, но обращаются с памятью по-разному: int занимает обычно 4 байта, double — 8. Тип указателя говорит компилятору, сколько байт читать по адресу и как их толковать.
Шаг 3. Разыменование — получить значение по адресу
Чтобы из адреса добраться до самого значения, ставят * перед указателем. Это называется разыменованием.
#include <iostream>
int main() {
int age = 30;
int* pAge = &age;
std::cout << "Значение по адресу: " << *pAge << "\n"; // 30
*pAge = 42; // меняем значение через указатель
std::cout << "Теперь age равно: " << age << "\n"; // 42
return 0;
}
Проверка: первая строка выведет 30, вторая — 42. Обратите внимание: мы не трогали age напрямую, а изменили значение через указатель — и это отразилось на самой переменной. Потому что pAge указывает именно на неё.
Звёздочка живёт в двух ролях. В объявленииint* pона часть типа: «p — указатель». В выражении*pэто операция: «дай значение по адресу». Одинаковый символ, разный смысл — отсюда половина путаницы у новичков.
Шаг 4. Указатель, который никуда не указывает
Указатель можно оставить пустым — присвоить ему nullptr. Это явный способ сказать «пока указывать не на что».
#include <iostream>
int main() {
int* p = nullptr;
if (p == nullptr) {
std::cout << "Указатель пуст, разыменовывать нельзя\n";
} else {
std::cout << *p << "\n";
}
return 0;
}
Проверка: выведется строка про пустой указатель. Если бы вы написали *p при p == nullptr, программа обратилась бы к несуществующему адресу и почти наверняка упала бы с ошибкой сегментации (Segmentation fault на Linux). Проверка на nullptr перед разыменованием — базовая привычка.
До C++11 вместо nullptr писали NULL или 0. В новом коде используйте только nullptr: он строго типизирован и не путается с целым числом.
Указатель против ссылки
Рядом с указателями всегда всплывает ссылка (int&). Внешне они решают похожую задачу — работать с чужой переменной, — но ведут себя по-разному.
| Признак | Указатель int* | Ссылка int& |
|---|---|---|
| Может быть пустым | Да, nullptr | Нет, всегда привязана к объекту |
| Можно переназначить на другой объект | Да | Нет, привязка на всю жизнь |
| Нужно разыменовывать | Да, *p | Нет, работает как обычное имя |
| Обязательна инициализация при объявлении | Нет (но лучше сразу дать значение) | Да |
int x = 10;
int& ref = x; // ref — второе имя для x
ref = 20; // теперь x == 20, без всякой звёздочки
int* ptr = &x; // ptr — адрес x
*ptr = 30; // теперь x == 30, разыменование обязательно
Простое правило на первое время: если сущность обязана всегда указывать на существующий объект и не меняться — берите ссылку. Если может быть «ничего» или должна переключаться между объектами — берите указатель.
Зачем это на практике
Самый частый повод — передать функции переменную, чтобы функция её изменила. По значению функция получает копию, и оригинал не тронут.
#include <iostream>
void tryByValue(int n) { n = 99; } // меняет копию
void changeByPointer(int* n) { *n = 99; } // меняет оригинал
int main() {
int score = 10;
tryByValue(score);
std::cout << score << "\n"; // 10, ничего не изменилось
changeByPointer(&score);
std::cout << score << "\n"; // 99
return 0;
}
Проверка: две строки — 10 и 99. В современном C++ для этой задачи чаще берут ссылку (void change(int& n)), она короче и безопаснее. Но понимать механику через указатель нужно: на ней держится вся работа с динамической памятью и низкоуровневым кодом.
Если не получилось
Segmentation faultпри запуске — почти всегда разыменование пустого или «мусорного» указателя. Проверьте, что указателю присвоен адрес реального объекта до того, как вы пишете*p.error: invalid conversion from 'int*' to 'int'— вы пытаетесь присвоить адрес обычной int-переменной или наоборот. Следите за звёздочкой в типе.- warning:
'p' is used uninitialized— объявили указатель, но не дали ему значения, а уже разыменовываете. Инициализируйте:int* p = nullptr;или сразу адресом. - Программа компилируется, но выводит случайные числа — указатель указывает на область, которая уже освобождена или никогда не была вашей. Такой указатель называют висячим.1
error: expected primary-expressionоколо&— вероятно, взяли адрес выражения, а не переменной:&(a + b)так не работает.
1 Висячий указатель (dangling pointer) — указатель, который хранит адрес уже несуществующего объекта. Обращение по нему — неопределённое поведение: программа может упасть, а может тихо выдать мусор, что хуже.
Что делать дальше
Вы разобрались с базовой механикой. Дальше по нарастанию сложности стоит взять три темы, но по одной за раз, не всё сразу:
- Динамическая память:
newиdelete, и почему за каждымnewдолжен следоватьdelete. Здесь начинаются утечки памяти. - Умные указатели:
std::unique_ptrиstd::shared_ptrиз заголовка<memory>. В современном C++ ручнойnew/deleteв прикладном коде почти не пишут — за освобождение отвечают умные указатели. Именно с них стоит начинать реальные проекты. - Связь указателей и массивов: имя массива в большинстве выражений превращается в указатель на первый элемент. Отдельная тема, здесь её не трогаем.
Соберите все примеры из статьи с флагом -Wall -Wextra и добейтесь, чтобы не было ни одного предупреждения. Умение читать предупреждения компилятора экономит больше времени, чем любой учебник.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: cppreference — Pointer declaration, cppreference — nullptr
Частые вопросы
В чём разница между int* p и int *p?
Что будет, если разыменовать nullptr?
У меня старый компилятор без C++17, что делать?
Указатели или ссылки — что использовать?
Почему программа компилируется, но выводит случайные числа вместо ожидаемого значения?
Зачем вообще нужны сырые указатели, если есть ссылки и умные указатели?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.