Ссылки
Концепты
Заголовок раздела «Концепты»- rvalue, lvalue
- Ссылка
Вопросы на понимание
Заголовок раздела «Вопросы на понимание»Что произойдет при запуске этого кода? Почему?
Там, где
mainне был расписан, код помещается в типичный основной файл:#include <iostream>#include <array> // надо для std::arrayint main(){// сюда}
1. Присваивание числа к числу
Заголовок раздела «1. Присваивание числа к числу»5 = 6;Ответ
5 это rvalue, ему нельзя присваивать значение, потому что оно не является объектом (lvalue).
2. Адрес числа
Заголовок раздела «2. Адрес числа»int* a = &5;Ответ
Нельзя брать адрес rvalue. Можно брать адрес памяти только от lvalue (выражений, результат которого есть объект), поскольку числа сами по себе не хранятся в памяти.
3. Адрес выражения
Заголовок раздела «3. Адрес выражения»int* a = &(5 + 6);Ответ
Аналогичная ситуация, (5 + 6) это rvalue.
Невозможно взять адрес rvalue.
4. Ссылка из числа
Заголовок раздела «4. Ссылка из числа»int& a = 5;Ответ
В ссылочную переменную можно записать только lvalue (ссылку на объект),
поскольку a должна будет ссылаться по итогу на ячейку памяти (объект),
а не просто на значение.
5. Ссылка из переменной
Заголовок раздела «5. Ссылка из переменной»int a = 5;int& b = a;std::cout << a << std::endl;std::cout << b << std::endl;Ответ
Здесь, в b сохраняется ссылка на a.
Под капотом, компилятор либо определит b как просто другое имя для a,
либо по факту сохранит в b указатель на a.
При считывании, использование имени b аналогично использованию имени a
— они обе ссылаются на тот же объект.
Их отличие в том, что a для создасться и хранилище при выполнении инструкции,
то есть выделится стековая память под объект.
Напечатается 5 2 раза.
6. Присваивание числа к ссылке
Заголовок раздела «6. Присваивание числа к ссылке»int a = 5;int& b = a;b = 6;std::cout << a << std::endl;Ответ
a тут перезапишется на 6 через имя b.
7. Перезапись переменной, на которую существует ссылка
Заголовок раздела «7. Перезапись переменной, на которую существует ссылка»int a = 5;int& b = a;a = 6;std::cout << b << std::endl;Ответ
a перезаписывается напрямую.
Поскольку b является ссылкой на a (например, хранит в себе адрес a)
считывание из b считает из a.
8. Ссылка из выражения
Заголовок раздела «8. Ссылка из выражения»int a = 5;int& b = a + 7;std::cout << a << std::endl;std::cout << b << std::endl;Ответ
a + 7 это rvalue, а не lvalue.
Поскольку нельзя получить ссылку на rvalue, данный код не скомпилируется.
9. Оператор с перезаписью примененный на ссылку
Заголовок раздела «9. Оператор с перезаписью примененный на ссылку»int a = 5;int& b = a;b += 7;std::cout << a << std::endl;std::cout << b << std::endl;Ответ
b как бы заменяется на a.
Здесь обновится a.
Так как b ссылается на a, напечатается то же для обеих переменных (12).
10. Присваивание переменной ссылке
Заголовок раздела «10. Присваивание переменной ссылке»int a = 1;int b = 2;int& c = a;c = b;std::cout << a << std::endl;std::cout << b << std::endl;std::cout << c << std::endl;Ответ
c = b не перезаписывает ссылку. Ссылку невозможно перезаписать.
В c = b вы как бы вставляете вместо c то, на что она ссылается (a),
получая a = b, что перезаписывает a на значение из b, 2.
Выведется:
22211. Перезапись ссылки на число после присвоения ей переменной
Заголовок раздела «11. Перезапись ссылки на число после присвоения ей переменной»int a = 1;int b = 2;int& c = a;c = b;c = 3;std::cout << a << std::endl;std::cout << b << std::endl;std::cout << c << std::endl;Ответ
Ссылки, в контраст с указателями, невозможно “перенаправить”.
Здесь иллюстрируется тот факт, что c продолжает ссылаться на a,
даже после строчки c = b.
Выведется:
32312. Ссылка из dereference-а
Заголовок раздела «12. Ссылка из dereference-а»int a = 1;int* pa = &a;int& b = *pa;*pa = 2;b = 3;std::cout << a << std::endl;std::cout << b << std::endl;std::cout << *pa << std::endl;Ответ
int& b = *pa; здесь ссылка происходит из указателя.
Все 3 имени теперь указывают на a.
Выведется 3 3 раза.
13. Перезапись указателя на адрес ссылки
Заголовок раздела «13. Перезапись указателя на адрес ссылки»int a = 1;int b = 2;int* p = &a;int& r = *p;p = &b;r = 3;std::cout << a << std::endl;std::cout << b << std::endl;Ответ
Изменится a, а не b. Выведется:
3214. Перезапись переменной через адрес и ссылка
Заголовок раздела «14. Перезапись переменной через адрес и ссылка»int a = 1;int& b = a;int* c = &b;*c = 2;std::cout << a << std::endl;Ответ
Из ссылки можно также получать адрес.
Здесь, *c = 2; перезапишет a.
15. Ссылки на поля
Заголовок раздела «15. Ссылки на поля»#include <iostream>
struct Position{ int x; int y;};
int main(){ Position a{ .x = 1, .y = 2 };
Position& ra = a;
int& rx = ra.x; ra.x = 3;
ra.y = 4; int& ry = ra.y;
std::cout << rx << std::endl; std::cout << ry << std::endl;
std::cout << ra.x << std::endl; std::cout << ra.y << std::endl;
std::cout << a.x << std::endl; std::cout << a.y << std::endl;}Ответ
Ссылки можно создавать на объекты внутри бОльших объектов.
Тут везде напечатается 3 и 4.
16. Ссылка на адрес (1)
Заголовок раздела «16. Ссылка на адрес (1)»int a = 1;int b = 2;int* pa = &a;int*& rpa = pa;pa = &b;*rpa = 3;std::cout << a << std::endl;std::cout << b << std::endl;Ответ
Можно создавать ссылки на указатели, потому что указатели тоже объекты (адреса в ячейках памяти).
Тут мы перезаписывает адрес сохраненный в pa напрямую,
а считывает адрес по ссылке.
*rpa -> *pa -> *(&b) -> b
В a будет 1, а в b будет 3.
17. Ссылка на адрес (2)
Заголовок раздела «17. Ссылка на адрес (2)»int a = 1;int b = 2;int* pa = &a;int*& rpa = pa;pa = &b;int& rb = *rpa;rb = 3;
std::cout << a << std::endl;std::cout << b << std::endl;Ответ
Здесь иллюстрируется тот факт, что можно создавать ссылки исходя из любых выражений, которые по итогу дают ссылку.
rb = 3 впишет 3 в b.
18. Размер ссылки
Заголовок раздела «18. Размер ссылки»Возможно ли у ссылки взять размер? Какой размер у ссылки?
#include <iostream>
int main(){ int a = 6; int& b = a; std::cout << sizeof(b) << std::endl;}Ответ
sizeof(b) сработает, но даст размер int-а, а не ссылки.
Размер ссылки получить невозможно, это скрыто компилятором.
Однако, если ссылка передается параметром в функцию, по факту передастся адрес, размер которого 8 байт.
Адрес необходимо передать, потому что функция не сможет получить доступ к той что надо переменной без внешнего контекста. Самый простой способ получить доступ к переменной, определенной извне — это передать ее адрес.
19. Пример использования ссылок (1)
Заголовок раздела «19. Пример использования ссылок (1)»#include <iostream>
void resetAmount(int& amount){ amount = 0;}int main(){ int appleAmount = 6; resetAmount(appleAmount); std::cout << appleAmount << std::endl;}Ответ
appleAmount перезапишется на 0, так как функции передастся ссылка на нее.
Под капотом, передается адрес переменной appleAmount, но это скрывается компилятором.
20. Пример использования ссылок (2)
Заголовок раздела «20. Пример использования ссылок (2)»#include <iostream>
struct Arm{ int power;};
void increasePower(Arm& arm){ arm.power += 1;}
int main(){ Arm arm { 1 }; increasePower(arm); std::cout << arm.power << std::endl;}Ответ
В функцию можно передавать ссылки на объекты типа структуры. Здесь задача функции — увеличить силу.
21. auto делает копию
Заголовок раздела «21. auto делает копию»int a = 5;auto b = a;b = 6;std::cout << a << std::endl;Ответ
auto выводит тип (int), но не ссылку:
b — это отдельный объект с копией a.
b = 6 перезапишет только копию, поэтому выведется 5.
22. auto& делает ссылку
Заголовок раздела «22. auto& делает ссылку»int a = 5;auto& b = a;b = 6;std::cout << a << std::endl;Ответ
auto& выводит int, но остается ссылкой:
b — это другое имя для a.
b = 6 перезаписывает a через это имя, поэтому выведется 6.
Используйте auto&, когда хотите изменить оригинал —
например, элементы массива в цикле, —
а const auto&, когда хотите лишь считать его.