Ссылки, lvalue и rvalue
Чему вы научитесь:
- объявлять ссылку как второе имя переменной;
- отличать lvalue от rvalue и понимать, к чему привязывается
int&, а к чемуconst int&; - брать адрес переменной и менять её через указатель;
- читать объявления
const int*иint* const.

Память компьютера похожа на длинную улицу из одинаковых домов. Каждая переменная — это дом, а значение лежит внутри. Имя переменной — табличка на двери: по ней вы находите нужный дом.
На один дом можно повесить вторую табличку. Дом от этого не раздваивается: по любой из двух табличек вы придёте к одной и той же двери. В C++ такая вторая табличка называется ссылкой.
Вы уже встречали ссылку в параметрах функции. Но её можно объявить и как обычную переменную — тогда она становится вторым именем другой переменной:
#include <iostream>
int main() {
int score = 10;
int& ref = score;
ref = ref + 5;
std::cout << score << '\n'; // 15
}Давайте разберём пример по строкам:
int score = 10;строит дом с табличкойscoreи кладёт в него10.int& ref = score;вешает на тот же дом вторую табличкуref. Нового дома нет, копии значения тоже.ref = ref + 5;читает10черезrefи кладёт туда же15.- Программа печатает
15:refиscoreобозначают одно место в памяти.
Ссылка, инициализированная другой ссылкой, привязывается к той же переменной: после int& alias = ref; имя alias тоже обозначает score. Это третья табличка на том же доме.
Одна переменная навсегда
Табличку прибивают к дому один раз — при объявлении ссылки. Поэтому ссылка без инициализатора не компилируется: int& ref; — ошибка. Перевесить табличку на другой дом потом тоже нельзя. Присваивание через ссылку меняет значение в доме, к которому она прибита:
#include <iostream>
int main() {
int first = 10;
int second = 20;
int& ref = first;
ref = second;
std::cout << first << ' ' << second << '\n'; // 20 20
}Строка ref = second; не перевешивает табличку ref на дом second. Она берёт значение second, то есть 20, и кладёт его в дом first. После неё ref по-прежнему обозначает first: если потом изменить second, ни first, ни ref не изменятся. Этот вопрос любят задавать на собеседовании.
К чему привязывается ссылка
Выражение, которое обозначает переменную, называют lvalue: например, score или ref. У такого выражения есть дом, и на него можно повесить табличку. Литерал 5 или результат score + 1 — это rvalue: значение без имени, которое не хранится ни в одной переменной. Дома у него нет, поэтому ссылка int& к нему не привязывается.
Константная ссылка const int& принимает и rvalue. Для такого значения компилятор строит временный объект — безымянную переменную, в которой это значение хранится. К этому объекту он и привязывает ссылку. Такой объект живёт, пока существует ссылка:
#include <iostream>
int main() {
int score = 10;
const int& next = score + 1;
score = 50;
std::cout << next << '\n'; // 11
}next обозначает временный объект со значением 11, а не score, поэтому изменение score его не задевает. По той же причине параметр const int& принимает литералы и выражения.
Здесь аналогия с табличками заканчивается: у временного объекта своего дома не было, и компилятор строит его специально для ссылки.
Частая ошибка
Обычную ссылку привязывают к результату выражения:
int main() {
int a = 2;
int b = 3;
int& sum = a + b; // ✗
return sum;
}error: non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'a + b — rvalue: сумма не хранится ни в одной переменной, поэтому int& к ней не привязывается. Если менять sum не нужно, объявите её как const int&. Но чаще здесь нужна обычная переменная: int sum = a + b;.
Главное
- Ссылка — второе имя переменной: обе таблички ведут к одному дому.
- Ссылку привязывают при объявлении, и перепривязать её нельзя:
ref = second;копирует значение. int&привязывается только к lvalue, аconst int&принимает и rvalue через временный объект.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.