Практический курс по C/C++: https://stepik.org/course/193691
Следующее важное
нововведение языка С++ - это ссылки. Объявляются они так же, как и обычные
переменные, только после типа следует поставить символ амперсанда. Например:
int d = 10;
int& lnk_d = d; // ссылка с именем lnk_d на переменную d
или, что то же
самое:
int& lnk_d2 {d};
int& lnk_d3 (d);
В итоге, с
ячейками памяти, где хранится значение переменной d, связано теперь
два имени: d и lnk_d. Ссылку можно
воспринимать, как неявный указатель, который хранит адрес переменной, записанной
при инициализации. В результате, мы через ссылку lnk_d можем выполнять
все те же самые действия, что и с переменной d. Например:
lnk_d = 5; // переменная d = 5
d = -1; // ссылка lnk_d связана со значением -1
lnk_d *= 10; // значение d увеличено в 10 раз
lnk_d++; // инкремент переменной d
Причем одна
ссылка может быть связана только с одной переменной (с одним элементом данных)
и связь эта прописывается в момент ее инициализации. В частности, это означает,
что объявить ссылку без инициализации в С++ нельзя. Следующая строчка приведет
к ошибке:
При этом в
инициализаторе можно прописывать любое допустимое lvalue выражение.
Например:
int a = 10;
int *ptr = &a;
int ar[] = {1, 2, 3};
int& lnk_1 = a; / / ok
int& lnk_2 = *ptr; // ok
int& lnk_3 = ar[1]; // ok
int& lnk_4 = 10; // ошибка
int& lnk_5 = ptr; // ошибка
То есть, ссылка
должна быть связана с областью памяти, где хранятся данные того же типа, который
указан при объявлении ссылки.
Некоторые из
вас, возможно, задаются вопросом, зачем вообще нужны ссылки, когда с переменной
(данными) можно работать напрямую через их имена? На самом деле, польза ссылок
проявляется там же, где и польза указателей. Например, в классической задаче
обмена значениями двух переменных. Без использования ссылок мы могли бы для
этой цели объявить следующую функцию:
void swap_d(double* x, double* y)
{
double t = *x;
*x = *y;
*y = t;
}
И, затем, в
функции main() вызвать ее
для переменных типа double:
double a{1.2}, b{-3.4};
swap_d(&a, &b);
std::cout << a << " " << b << std::endl;
Как видите, при
вызове в аргументах функции swap_d() дополнительно
приходится указывать операцию взятия адреса переменных a и b. Это не очень
удобно и красиво. Куда лучше было бы записать эту же функцию с использованием
ссылок:
void swap_d(double& x, double& y)
{
double t = x;
x = y;
y = t;
}
И ее последующий
вызов:
Так все выглядит
куда более естественно. Кроме того, ссылки куда более безопаснее указателей,
так как мы уже знаем, что они могут быть инициализированы только корректными lvalue выражениями, а
значит, работают с ячейками памяти, в которых хранятся данные соответствующего типа.
Все это делает ссылки более удобным и безопасным инструментом, чем указатели.
Поэтому часто, где это возможно, они заменяют указатели.
Также обратите
внимание, что при объявлении параметров функции в виде ссылок их не нужно
инициализировать. Компилятор поставит им в соответствие те переменные, которые
мы укажем при вызове этой функции. Причем, сами переменные при этом не
копируются, а лишь формируется связь между параметрами-ссылками и переменными,
указанными при вызове функции. В частности, это означает, что если нужно
передать в функцию какие-либо объемные данные, например, большие структуры, то
часто целесообразно это сделать через ссылки.
Другое
назначение ссылок – это возможность изменения данных, где другие инструменты не
очень удобны в использовании. Например, дан массив:
short p[] = {1, 2, 3, 4};
и нам нужно
значения всех его элементов увеличить в два раза. Логично было бы
воспользоваться новой формой записи цикла for следующим
образом:
for (short& x : p)
x *= 2;
И выведем
полученный массив p в консоль:
for (int i = 0; i < sizeof(p)/sizeof(*p); ++i)
std::cout << p[i] << " ";
Увидим:
2 4 6 8
Почему здесь в
первом цикле for нужно
прописывать переменную x в виде ссылки? Думаю, вы уже
догадались, что в противном случае:
for (short x : p)
x *= 2;
мы получим
копирование текущего значения массива p в переменную x и изменение
(увеличение в 2 раза) коснется этой новой переменной, а не элемента массива.
Ссылка же решает нашу задачу.
Константные ссылки
В языке С++
допустимо объявлять константные ссылки, то есть, ссылки, с помощью которых
можно только читать значение переменной, но не менять. Например:
int s = 0;
const int& ls = s;
int x = ls; // чтение данных разрешено
ls = 5; // ошибка, запись нового значения невозможна
Соответственно,
константные ссылки могут быть инициализированы, как обычными переменными, так и
константными:
int s = 0;
const int d = -2;
const int& ls = s;
const int& ld = d;
А вот обычная
ссылка может вести только на такую же обычную переменную:
int s = 0;
const int d = -2;
int& ls = s; // ok
int& ld = d; // ошибка
Это вполне
очевидное ограничение, т.к. иначе бы мы смогли изменить константную переменную d через ссылку ld.
Когда нам могут
понадобиться константные ссылки? Самое очевидное – это описание неизменяемых
параметров функции. Давайте представим, что у нас имеется структура, которая
описывает точки в двумерном пространстве следующим образом:
struct point {
char name[50]; // название точки
double x, y; // координаты точки
};
Затем, объявим
функцию, которая вычисляет длину такого радиус-вектора:
double length(const point& p)
{
return sqrt(p.x * p.x + p.y * p.y);
}
И вызовем ее в
функции main():
int main()
{
point p2 {"first", 10.0, 20.0};
double len = length(p2);
std::cout << len << std::endl;
return 0;
}
Смотрите, в
момент вызова функции length(p2), структура p2 не копируется
в параметр p, копируется
только ее адрес. В результате ссылка p связывается со структурой p2. А
дополнительное ключевое слово const гарантирует, что структура p2 никак не будет
менять свое состояние внутри функции length().
Соответственно, программист, который использует эту функцию, понимает, что если
какой-либо ее параметр помечен как const, то он может
быть уверенным в неизменности передаваемого аргумента.
И еще раз
отмечу, что если бы в функции length() передача аргумента происходила
бы не по ссылке, а по значению:
double length(const point p) ...
то выполнялось
бы полное побайтовое копирование структуры p2 в переменную p, в результате
программа работала бы дольше, но результат был бы тем же. Поэтому везде, где
происходит передача объемных данных в параметры функции, целесообразно подумать
об использовании ссылок или указателей.
Практический курс по C/C++: https://stepik.org/course/193691