Два способа сослаться на нечто
Вы уже знаете указатели — переменные, которые хранят адрес и позволяют добраться до объекта, живущего по этому адресу. Ссылки — это другой инструмент, который C++ даёт вам для косвенной работы с существующим объектом. Они пересекаются настолько, что новички часто не могут понять, что выбрать, поэтому эта страница ставит их рядом.
Если коротко: ссылка — это псевдоним. После выполнения int& r = x; r и есть x — тот же объект, другое имя. Указатель — это отдельный объект, который просто хранит адрес другого. Это единственное различие определяет всё остальное.
Ссылка — это псевдоним
Ссылка должна быть привязана к объекту в момент создания, и с этого момента каждое использование ссылки затрагивает оригинал.
Обратите внимание: в месте использования нет ни * для разыменования, ни & для «взятия адреса» — вы читаете и пишете alias ровно так же, как обычный int. & в int& alias — это часть типа, а не оператор взятия адреса.
Чем они различаются
Поведение ниже — это и есть причина, по которой существуют оба инструмента. Эту таблицу стоит запомнить.
// reference pointer
// must be initialized? yes no (but should be)
// can be null? no yes (nullptr)
// can be reseated? no yes
// pointer arithmetic? no yes
// syntax to use it just the name *p or p->member
// taking address &ref == &original &p is the pointer's own address
Два пункта сбивают людей с толку сильнее всего. Во-первых, ссылку никогда нельзя перепривязать: присваивание ей копирует значение внутрь объекта, на который она ссылается, а не направляет ссылку на нечто новое.
Указатель, напротив, волен указывать в другое место в любой момент:
Во-вторых, ссылка никогда не может законно быть null, а указатель может. Это делает «отсутствие значения» представимым с помощью указателя, но не ссылки — на это свойство вы будете опираться постоянно.
Выбор в параметрах функций
Именно здесь выбор проявляется чаще всего. Когда функции нужно прочитать или изменить объект вызывающей стороны, подходят оба варианта, но они сигнализируют о разном намерении.
Версию со ссылкой (addTax(cart)) невозможно вызвать с «ничем», поэтому внутри функции вы никогда не проверяете на null — объект гарантированно на месте. Версия с указателем (applyDiscount(&cart)) через & объявляет в месте вызова, что аргумент может быть изменён, и позволяет вызывающему передать nullptr, означающий «неприменимо». Выбирайте тот вариант, чья гарантия соответствует вашей функции.
Для параметров крупных типов, доступных только для чтения, идиоматический выбор — const T&: он избегает копирования и обещает не изменять. Подробнее о передаче по значению и по ссылке см. параметры функций.
Простое практическое правило
Когда сомневаетесь, по умолчанию выбирайте ссылку и переходите к указателю только тогда, когда нужна возможность, которой у ссылки нет:
- Используйте ссылку, когда объект всегда существует и его идентичность никогда не меняется — обычный случай для параметров функций и псевдонимов.
- Используйте указатель, когда верно любое из перечисленного:
- «Ничего» является допустимым состоянием (необязательный аргумент, поиск, который может не найти совпадения) — указатель может быть
nullptr. - Нужно со временем указывать на разные объекты — указатель можно перепривязать.
- Вы управляете памятью в куче, которую освободите через
delete, или проходите по массиву с помощью арифметики указателей.
- «Ничего» является допустимым состоянием (необязательный аргумент, поиск, который может не найти совпадения) — указатель может быть
Если ничего из этого не применимо, ссылка — более чистый и безопасный выбор, потому что компилятор за вас обеспечивает «всегда корректна, никогда не перепривязывается».
Распространённые ошибки, которых стоит избегать
- Ожидать, что
ref = otherвыполнит перепривязку. Вместо этого присваивается значение внутрь объекта, на который указывает ссылка. Ссылки привязаны на всю жизнь; если нужна перепривязка, используйте указатель. - Возвращать ссылку (или указатель) на локальную переменную.
int& f() { int x = 5; return x; }возвращает висячую ссылку:xумирает при возврате изf, и использование результата — это неопределённое поведение. Та же ловушка касается указателей (return &x;). - Подделывать «нулевую ссылку». Запись
int& r = *p;, когдаpравенnullptr, — это неопределённое поведение в момент разыменования, а не безопасная «пустая» ссылка. Выражайте необязательность через указатель илиstd::optional. - Хвататься за указатель по привычке. Если аргумент всегда существует и вы никогда не будете его перепривязывать, ссылка устраняет целый класс проверок на null и падений. Не платите за возможности, которыми не пользуетесь.
Далее: Динамическая память
До сих пор каждый объект, на который вы ссылались или указывали, создавался автоматически в стеке. Следующая страница, динамическая память, охватывает new и delete: запрос памяти у операционной системы во время выполнения, почему ею владеют указатели (а не ссылки) и как забывчивость в её освобождении приводит к утечкам.
Часто задаваемые вопросы
В чём разница между ссылкой и указателем в C++?
Ссылка — это псевдоним существующего объекта: она должна быть инициализирована, никогда не может быть null и никогда не может быть впоследствии перенаправлена на другой объект. Указатель — это отдельная переменная, хранящая адрес: он может быть null, может быть перепривязан, чтобы указывать в другое место, и поддерживает арифметику указателей. Со ссылками используйте синтаксис &, а с указателями — */->.
Когда в C++ лучше использовать указатель вместо ссылки?
Используйте указатель, когда «ничего» является допустимым состоянием (необязательный аргумент, результат «не найдено»), когда нужно перепривязывать его со временем на разные объекты, или когда вы владеете памятью в куче, которую освободите через delete. Используйте ссылку, когда объект всегда существует и никогда не меняет свою идентичность — это покрывает большинство параметров функций.
Может ли ссылка быть null в C++?
Нет. Корректная ссылка всегда ссылается на реальный объект, поэтому вы никогда не проверяете её на null. Если вы создаёте ссылку из разыменованного нулевого указателя (int& r = *p;, где p равен null), вы получаете неопределённое поведение, а не нулевую ссылку. Когда нужно выразить «возможно, ничего», используйте указатель или std::optional.