Указатели против ссылок

Второй по частоте вопрос после стека и кучи: «В чём разница между указателем и ссылкой, если оба как будто дают доступ к чужой переменной?»

Ссылка — постоянный псевдоним уже существующей переменной, привязывается один раз и навсегда. Указатель — отдельная переменная, которая хранит адрес в памяти и может быть переназначена или обнулена.

Что выведет этот код?

#include <iostream>

int main() {
    int a = 10;
    int b = 20;

    int& ref = a;   // ссылка на a
    ref = b;          // это НЕ переназначение ссылки!

    std::cout << "a = " << a << std::endl;
    std::cout << "ref = " << ref << std::endl;
    return 0;
}

Вывод:

a = 20
ref = 20

Вот она, главная ловушка вопроса. Строка ref = b; выглядит так, будто теперь ref ссылается на переменную b. На самом деле нет: ссылку нельзя «перепривязать» после создания. Строка ref = b; означает «скопировать значение b в ту переменную, на которую указывает ref» — то есть фактически это a = b;. Именно поэтому a тоже стало равно 20: ссылка ref — это просто другое имя для a, и присвоение через это имя меняет саму a.

Тот же трюк с указателем — другое поведение

#include <iostream>

int main() {
    int a = 10;
    int b = 20;

    int* p = &a;   // указатель на a
    p = &b;          // а вот это — настоящее переназначение

    std::cout << "a = " << a << std::endl;
    std::cout << "*p = " << *p << std::endl;
    return 0;
}

Вывод:

a = 10
*p = 20

Здесь картина другая: p = &b; — это именно переназначение указателя, теперь он хранит адрес b, а не a. Переменная a при этом никак не пострадала и осталась равна 10. Указатель — самостоятельная переменная (он хранит число — адрес), поэтому его можно менять когда угодно, в отличие от ссылки, которая жёстко привязывается один раз при объявлении.

Как это работает под капотом

Указатель int* p в памяти занимает реальное место (обычно 8 байт на 64-битной системе) и хранит там число — адрес другой ячейки памяти. Чтобы получить значение, на которое он указывает, нужно явно «разыменовать» его звёздочкой: *p. Указатель можно не инициализировать сразу, можно обнулить через nullptr, можно переприсвоить сколько угодно раз, можно даже сделать арифметику вроде p + 1 (сдвинуть на следующий элемент массива).

Ссылка же на уровне компилятора почти всегда реализуется как тот же самый указатель «под капотом», но с ограничениями, которые компилятор соблюдает уже на этапе компиляции: она обязана быть инициализирована сразу при объявлении, не может быть nullptr и не может быть переприсвоена на другой объект. Из-за этих гарантий работать со ссылкой в коде проще и безопаснее — не нужно писать *, не нужно бояться нулевого указателя.

Таблица-памятка для собеседования

ПризнакУказательСсылка
Может быть nullда (nullptr)нет, обязана ссылаться на объект
Можно переназначитьда, в любой моментнет, привязывается один раз
Нужна инициализация сразунетда, обязательно
Синтаксис доступачерез *pкак у обычной переменной
Поддерживает арифметикуда (p + 1)нет

Частые ошибки на собеседовании

  • Говорят, что ссылку «можно переназначить, как указатель» — нет, присвоение ссылке всегда меняет объект, на который она ссылается, а не саму привязку.
  • Путают nullptr с пустой ссылкой — ссылок без объекта в корректном C++ не существует в принципе.
  • Не могут объяснить, зачем вообще нужны указатели, если есть более безопасные ссылки — ответ: указатели нужны там, где требуется «может отсутствовать» (null), «можно переприсвоить» или арифметика адресов, например при обходе массива или в реализации собственных структур данных.

Итог

  • Ссылка — второе имя для уже существующей переменной, привязка происходит один раз и навсегда.
  • Указатель — самостоятельная переменная-адрес, которую можно переназначать, обнулять и использовать в арифметике.
  • Присвоение ссылке (ref = b) меняет значение исходного объекта, а не «перепривязывает» ссылку.
Проверьте себя
1. После кода int a=10, b=20; int& ref=a; ref=b; чему равно значение переменной a?
A10, потому что ссылку нельзя изменить
B20, потому что присвоение через ссылку меняет исходный объект
CПрограмма не скомпилируется
DНеопределённое значение
2. Что из перечисленного верно только для указателя, но не для ссылки?
AМожет хранить адрес переменной
BМожет быть равен nullptr и переназначен на другой объект
CМожет использоваться для изменения значения переменной
DЗанимает место в памяти