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

Ссылки, lvalue и rvalue

Чему вы научитесь

Чему вы научитесь:

  • объявлять ссылку как второе имя переменной;
  • отличать lvalue от rvalue и понимать, к чему привязывается int&, а к чему const int&;
  • брать адрес переменной и менять её через указатель;
  • читать объявления const int* и int* const.
Улица из одинаковых домов; на двери одного дома две таблички, score и ref, а в его окне плитка 15

Память компьютера похожа на длинную улицу из одинаковых домов. Каждая переменная — это дом, а значение лежит внутри. Имя переменной — табличка на двери: по ней вы находите нужный дом.

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

Вы уже встречали ссылку в параметрах функции. Но её можно объявить и как обычную переменную — тогда она становится вторым именем другой переменной:

C++
#include <iostream>

int main() {
    int score = 10;
    int& ref = score;
    ref = ref + 5;
    std::cout << score << '\n';  // 15
}

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

  1. int score = 10; строит дом с табличкой score и кладёт в него 10.
  2. int& ref = score; вешает на тот же дом вторую табличку ref. Нового дома нет, копии значения тоже.
  3. ref = ref + 5; читает 10 через ref и кладёт туда же 15.
  4. Программа печатает 15: ref и score обозначают одно место в памяти.

Ссылка, инициализированная другой ссылкой, привязывается к той же переменной: после int& alias = ref; имя alias тоже обозначает score. Это третья табличка на том же доме.

Одна переменная навсегда

Табличку прибивают к дому один раз — при объявлении ссылки. Поэтому ссылка без инициализатора не компилируется: int& ref; — ошибка. Перевесить табличку на другой дом потом тоже нельзя. Присваивание через ссылку меняет значение в доме, к которому она прибита:

C++
#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. Для такого значения компилятор строит временный объект — безымянную переменную, в которой это значение хранится. К этому объекту он и привязывает ссылку. Такой объект живёт, пока существует ссылка:

C++
#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& принимает литералы и выражения.

Здесь аналогия с табличками заканчивается: у временного объекта своего дома не было, и компилятор строит его специально для ссылки.

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

Обычную ссылку привязывают к результату выражения:

C++
int main() {
    int a = 2;
    int b = 3;
    int& sum = a + b;  // ✗
    return sum;
}
TEXT
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 и подпишись на канал — откроются все уроки.

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