Найти тему
ZDG

От С к C++: Ссылки

Оглавление

В C++ добавился новый тип, который поначалу ввёл меня в ступор. Это ссылка (reference).

Предыдущая часть:

Я потратил довольно много времени, чтобы понять сущность ссылок, но в итоге оказалось, что я сильно переоценил их, считая чем-то особенным. Ссылки это просто указатели в другом синтаксисе, и на этом можно было закончить.

Но ведь их придумали зачем-то.

Давайте повторим прошлые материалы про указатели. Каждая переменная программы, вроде такой:

foo = 5

обладает следующими свойствами:

  • у неё есть имя foo
  • у неё есть значение 5

Переменная это хранилище, где хранится значение, а для доступа к хранилищу нужно имя. Но что такое имя? Ничто иное, как адрес. Чтобы получить значение, нужно обратиться по адресу.

Таким образом, если в адрес 1000 записать 5 и назвать этот адрес foo, то мы получим переменную с именем foo, со значением 5 и с адресом 1000.

Если бы язык C был абсолютно свободным и прямолинейным, нам вообще были бы не нужны типы-указатели. С любой переменной можно было бы работать так, как будто она одновременно является указателем.

Например,

foo = 5

это записать по адресу foo значение 5,

bar = &foo

это записать по адресу bar значение адреса foo,

*bar = 5

это записать по адресу, который хранится по адресу bar, значение 5.

Переменная bar в данном случае работает как указатель. Её можно было бы не объявлять как специальный тип-указатель. Достаточно было бы написать *bar.

Но тип-указатель объявлять приходится, поэтому создание переменной bar будет выглядеть так:

int* bar;

Где int* это указатель на адрес с размером данных int. Заметим, что часто тип-указатель пишут вот так:

int *bar;

Разницы нет никакой, но *bar это, как мы ранее видели, содержимое памяти, на которую указывает значение bar. Просто в этом случае запись трактуется как тип-указатель. Синтаксис неоднозначен, и я предлагаю угадать, что получится в этом примере:

int* foo;
int* bar = (int)foo**foo;

Чтобы не было разночтений, я рекомендую использовать именно такое объявление типа:

int* bar;

Ссылки

Ссылка и указатель в C это синонимы. Указатель ссылается на какой-то адрес, а ссылка указывает на какой-то адрес. Это технически полностью одно и то же.

Тип-ссылка объявляется так:

int& bar;

Просто другой символ. Который опять же не надо приписывать к имени переменной вот так:

int &bar;

Потому что &bar это адрес переменной bar и синтаксис опять неоднозначен.

Первое отличие ссылки от указателя в том, что ей обязательно надо присвоить значение:

int& bar = foo;

Здесь ссылке (указателю) bar присваивается адрес переменной foo. Ранее я говорил, что адрес переменной foo это &foo. Но когда мы работаем со ссылками, обычные правила синтаксиса уже не действуют. Когда мы присваиваем значение ссылке, то по умолчанию считается, что это уже адрес, поэтому & писать не надо.

То есть, вот эти две записи эквивалентны:

int* bar = &foo;

int& bar = foo;

И в том и в другом случае мы получаем адрес foo, записанный в переменную bar.

Также эквивалентны эти записи:

int bar = *foo; // если foo – указатель

int bar = foo; // если foo – ссылка

И в том и в другом случае мы получаем значение по ссылке/указателю foo.

Работа со ссылками

Сcылки работают точно так же, как указатели, просто синтаксис у них устроен наоборот. Как мы уже видели раньше, если указателю присваивается адрес переменной foo, то нужно писать &foo, а для ссылки не нужно. Если для доступа к полям структуры, расположенной по указателю, нужно использовать стрелки:

bar->x = 5;

То для ссылки нужно использовать точку:

bar.x = 5;

Вот программа с двумя способами передачи параметра в функцию – по ссылке и по указателю:

Функции area_ref() и area_ptr() абсолютно идентичны друг другу:

-2

То есть разница между указателем и ссылкой исключительно в синтаксисе.

Ссылка это такой способ замаскировать указатель под обычную переменную. То есть не писать & при получении адреса, не писать * при получении значения по указателю, и не ставить стрелочек при работе с полями структуры.

Для чего это надо – сказать затрудняюсь. Два вида указателей с одним и тем же функционалом только лишь вносят путаницу и неоднозначность в трактовке синтаксиса.

Но у ссылки есть дополнительные правила – она должна быть обязательно инициализирована и ей нельзя присвоить NULL. Это может пригодиться для создания более надёжного кода.

Но случаев, когда именно ссылка может сделать что-то, чего не может указатель, я не нашёл. Точнее, эти случаи, предположительно, относятся к перегрузке операторов и каким-то особым конструкторам, и базируются исключительно на синтаксисе, но до этого ещё надо дойти.

Пока же просто – когда вы видите ссылку, это на самом деле обычный указатель.

Поправки из комментариев

Я упустил один момент с присваиванием ссылок, на который мне указали более умные комментаторы :)

Попробуем переприсвоить значения указателя и ссылки:

-3

После того как мы присвоили указателю ptr адрес переменной &a, мы можем присвоить тому же указателю адрес &b. Это выльется в то, что собственное значение ptr будет равно адресу a, а затем изменится на адрес b. При этом значения переменных a и b останутся как и были.

В случае со ссылкой ref вначале всё так же: в ref сохраняется значение адреса a. Но когда мы пытаемся записать в уже инициализированную ref адрес переменной b, это сделать уже невозможно. Потому что такая запись:

ref = b;

С точки зрения указателя равносильна такой:

*ptr = b;

То есть мы не в ref записываем адрес b, а в адрес, находящийся в ref, записываем значение b.

В результате значение ref остаётся равным 1000, и по этому адресу, то есть в переменную a, записывается значение переменной b. То есть теперь a = 6.

Это чисто синтаксическая ловушка. Физически мы ничем не ограничены, но нет таких синтаксических средств, чтобы поменять адрес в ref, который был однажды задан при инициализации. Поменять можно только значение по адресу.

Наука
7 млн интересуются