Перейти к содержимому
Теория · Ссылки и указатели · урок 2 из 9

Указатели, адреса и nullptr

В прошлом уроке ссылка была второй табличкой на двери дома. Табличку прибивают один раз, и без дома её не бывает.

У каждого дома на улице памяти есть номер — его адрес. Адрес можно записать на бумажку. По бумажке вы придёте к дому, заглянете внутрь или что-то в нём поменяете. А потом бумажку можно стереть и написать на ней адрес другого дома. Можно и оставить её пустой, если идти пока некуда.

Такая бумажка в C++ называется указателем. В этой программе один указатель p по очереди меняет две переменные:

C++
#include <iostream>

int main() {
    int a = 1;
    int b = 2;
    int* p = &a;
    *p = 10;
    p = &b;
    *p = 20;
    std::cout << a << ' ' << b << '\n';  // 10 20
}

Давайте разберём пример по строкам:

  1. int a = 1; и int b = 2; строят два дома.
  2. int* p = &a; записывает на бумажку p адрес дома a.
  3. *p = 10; идёт по адресу с бумажки и кладёт 10 в дом a.
  4. p = &b; стирает бумажку и пишет на ней адрес дома b.
  5. *p = 20; идёт уже к дому b и кладёт туда 20.
  6. Программа печатает 10 20: через один указатель изменились обе переменные.

Адрес и разыменование

Указатель — переменная, которая хранит адрес другой переменной, то есть номер места этой другой переменной в памяти. Тип int* читается как «указатель на int». Выражение &a даёт адрес a, поэтому int* p = &a; кладёт в p адрес a.

Выражение *p обозначает саму переменную, адрес которой хранится в p. Такой доступ называют разыменованием: *p = 10; меняет a. Строка p = &b; меняет сам указатель, то есть переписывает бумажку, и следующее *p = 20; меняет уже b.

Сравните это со ссылкой: ref = b; не перевешивает табличку, а копирует значение. Пройдите программу по шагам и следите, к какому дому ведут ref и p после каждой строки:

ВживуюСсылка и указатель: кто куда ведётшаг 1 из 9 · рамок: 1

Дом a, внутри 1

Шагайте по строкам и следите за ref и p

Символы & и * значат разное в объявлении и в выражении:

  • в объявлении int& — ссылка;
  • в объявлении int* — указатель;
  • в выражении &a — адрес a;
  • в выражении *p — переменная, на которую указывает p.

Нулевой указатель

Указателю, которому пока не на что указывать, присваивают значение nullptr, и такой указатель называют нулевым указателем. Это пустая бумажка. Например, именно так функция поиска сообщает, что ничего не нашла. Перед разыменованием такой указатель проверяют:

C++
int* found = nullptr;
if (found != nullptr) {
    std::cout << *found << '\n';
}

Строка if (found != nullptr) спрашивает: есть ли на бумажке адрес? Только если есть, программа идёт по нему и печатает значение.

За nullptr нет переменной, и читать через него нечего. Разыменование нулевого указателя — неопределённое поведение. Компилятор такую программу собирает, а стандарт не говорит, что она сделает. Не гарантировано даже падение программы.

Здесь аналогия с бумажкой заканчивается: с пустой бумажкой человек никуда не пойдёт, а программа с *found на нулевом указателе может сделать что угодно.

Частая ошибка

Указатель объявили без начального значения и сразу разыменовали:

C++
int main() {
    int* p;
    *p = 5;  // ✗
}
TEXT
warning: variable 'p' is uninitialized when used here [-Wuninitialized]

Компилятор только предупреждает. Значение p не задано: на бумажке случайные каракули, и запись по такому адресу — неопределённое поведение. Если адреса ещё нет, инициализируйте указатель значением nullptr: тогда проверка p != nullptr покажет, что он пуст.

Главное

  • Указатель хранит адрес переменной: &a берёт адрес, а *p — сама переменная по этому адресу.
  • Указатель можно перенаправить: p = &b; переписывает адрес, а *p = 20; меняет переменную.
  • nullptr — пустой указатель; его проверяют перед разыменованием, иначе неопределённое поведение.
  • Указатель без начального значения разыменовывать нельзя — инициализируйте его хотя бы nullptr.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом