Прежде чем описывать ссылки, давайте вспомним прошлую лекцию и распишем недостатки указателей (возможно список не полон)
*
и &
)Все это привело к тому, что в языке С++ появилось более дружелюбный тип данных, лишенных данных недостатков. Имя ему - ссылка. Ссылка — это тип переменной в языке C++, который работает как псевдоним другого объекта или значения. Ссылка (на неконстантное значение) объявляется с использованием амперсанда (&
) между типом данных и именем ссылки:
int value = 7; // обычная переменная
int& ref = value; // ссылка на переменную value
Для сравнения удобства приведем пример из прошлой лекии по обмену значений переменных, но уже с использованием ссылок:
void swap(int& a, int& b)
{
int t = b;
b = a;
a = t;
}
int main()
{
int k = 10, m = 20;
swap(k, m);
cout << k << ' ' << m << endl; // 20 10
return 0;
}
Как видно из примера - нет больше необходмости просизводить операции взятия адреса и значения, если мы хотим работать с оригинальной переменной (то есть с переменными из функции main
при передачи в функцию swap
). Вместо этого достаточно указать в объявлении функции swap
что объекты будут переданы не по значению, а по ссылке, указав тип как ссылочный (то есть добавив &
).
При этом ссылки, фактически работают и как “псевдонимы” переменных - измения их значения мы изменяем значения оригинальных переменных.
#include <iostream>
using namespace std;
int main()
{
int value = 7;
int& ref = value; // ссылка на переменную value
value = 8; // value теперь 8
ref = 9; // value теперь 9
cout << value << endl; // 9
++ref;
cout << value << endl; // 10
return 0;
}
При этом при использоавнии оператора взятия адреса (&
) у ссылке и у ссылоемой переменной вернется одинаковый адрес.
cout << &value; // 0x11FF22DD
cout << &ref; // 0x11FF22DD
int *p; // OK
int &l; // ошибка
int *p = nullptr; // OK
int &l = 0; // ошибка
int a = 10, b = 20;
int *p = &a; // p указывает на a
p = &b; // p указывает на b
int &l = a; // l ссылается на a
l = b; // a присваивается значение b
int a = 10;
int *p = &a; // p указывает на a
int **pp = &p; // pp указывает на переменную p
int &l = a; // l ссылается на a
int *pl = &l; // pl указывает на переменную a
int &&ll = l; // ошибка
int *mp[10] = {}; // массив указателей на int
int &ml[10] = {}; // ошибка
В языке C++ все переменные являются l-values. l-value - выражения, значения которых являются ссылкой на переменную/элемент массива, а значит могут быть указаны слева от оператора =
Противоположностью l-value является r-value. r-value — это значение, которое не имеет постоянного адреса в памяти. Примерами могут быть единичные числа (например, 7, которое имеет значение 7) или выражения (например, 3 + х, которое имеет значение х плюс 3).
Указатели и ссылки могут указывать только на l-value
int a = 10, b = 20;
int m[10] = {1, 2, 3, 4, 5, 5, 4, 3, 2, 1};
int &l1 = a; // OK
int &l2 = a + b; // ошибка
int &l3 = *(m + a / 2); // OK
int &l4 = *(m + a / 2) + 1; // ошибка
int &l5 = (a + b > 10) ? a : b; // OK
Как мы уже разбирали ранее все переменные передаются и возвращаются по значению. Соответственно, если мы хотим передавать значение по указателю эти переменные не должны уходить за рамки видимости. В противоположность к этому мы теперь можем передвать и возвращать результаты по ссылке. Проилюстрируем в качестве пример следющий код:
int* foo()
{
int a = 10;
return &a;
}
int& bar()
{
int b = 20;
return b;
}
// ..
int* p = foo(); // объект a был разрушен при выходе из foo(), соотв. *p ссылается на недействительный адрес
int& l = bar(); // объект b был возвращен по ссылке