Думайте о ссылке как псевдоним . Когда вы вызываете что-то для ссылки, вы действительно вызываете это для объекта, на который ссылается ссылка.
int i;
int& j = i; // j is an alias to i
j = 5; // same as i = 5
Когда дело доходит до функций, подумайте:
void foo(int i)
{
i = 5;
}
Выше int i
является значением, а переданный аргумент передается значением . Это означает, что если мы скажем:
int x = 2;
foo(x);
i
будет копией из x
. Таким образом, установка i
в 5 не влияет на x
, потому что это копия x
, которая изменяется. Однако, если мы сделаем i
ссылку:
void foo(int& i) // i is an alias for a variable
{
i = 5;
}
Тогда, сказав, foo(x)
больше не делает копию x
; i
- это x
. Так что, если мы скажем foo(x)
, внутри функции i = 5;
точно так же, как x = 5;
, и x
изменится.
Надеюсь, это немного прояснит.
Почему это важно? Когда вы программируете, вы никогда не хотите копировать и вставлять код. Вы хотите сделать функцию, которая выполняет одну задачу, и она делает это хорошо. Всякий раз, когда нужно выполнить эту задачу, вы используете эту функцию.
Допустим, мы хотим поменять местами две переменные. Это выглядит примерно так:
int x, y;
// swap:
int temp = x; // store the value of x
x = y; // make x equal to y
y = temp; // make y equal to the old value of x
Хорошо, отлично. Мы хотим сделать это функцией, потому что: swap(x, y);
гораздо легче читать. Итак, давайте попробуем это:
void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
Это не сработает! Проблема в том, что это замена копий двух переменных. То есть:
int a, b;
swap(a, b); // hm, x and y are copies of a and b...a and b remain unchanged
В C, где ссылки не существуют, решением было передать адрес этих переменных; то есть используйте указатели *:
void swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int a, b;
swap(&a, &b);
Это хорошо работает. Тем не менее, это немного неудобно в использовании, и на самом деле немного небезопасно. swap(nullptr, nullptr)
, заменяет две ничто и разыменовывает нулевые указатели ... неопределенное поведение! Исправлено с некоторыми проверками:
void swap(int* x, int* y)
{
if (x == nullptr || y == nullptr)
return; // one is null; this is a meaningless operation
int temp = *x;
*x = *y;
*y = temp;
}
Но выглядит как неуклюжий наш код. C ++ вводит ссылки для решения этой проблемы. Если мы можем просто псевдоним переменной, мы получим код, который мы искали:
void swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
int a, b;
swap(a, b); // inside, x and y are really a and b
И простой в использовании, и безопасный. (Мы не можем случайно передать нулевое значение, нулевых ссылок нет.) Это работает, потому что перестановка внутри функции действительно происходит с переменными, псевдонимы которых находятся вне функции.
(Обратите внимание, никогда не пишите swap
функцию. :) Одна из них уже существует в заголовке <algorithm>
, и она настроена на работу с любым типом.)
Другое использование - удалить эту копию, которая происходит при вызове функции. Представьте, что у нас очень большой тип данных. Копирование этого объекта занимает много времени, и мы хотели бы избежать этого:
struct big_data
{ char data[9999999]; }; // big!
void do_something(big_data data);
big_data d;
do_something(d); // ouch, making a copy of all that data :<
Однако все, что нам действительно нужно, это псевдоним переменной, поэтому давайте укажем это. (Опять же, в C мы передавали адрес нашего большого типа данных, решая проблему копирования, но вводя неуклюжесть.):
void do_something(big_data& data);
big_data d;
do_something(d); // no copies at all! data aliases d within the function
Вот почему вы услышите, что вам следует постоянно передавать ссылки, если только они не являются примитивными типами. (Поскольку внутренняя передача псевдонима, вероятно, выполняется с помощью указателя, как в C. Для небольших объектов копирование выполняется быстрее, чем при использовании указателей.)
Имейте в виду, что вы должны быть правильными. Это означает, что если ваша функция не изменяет параметр, пометьте его как const
. Если do_something
выше только посмотрел, но не изменился data
, мы пометили бы его как const
:
void do_something(const big_data& data); // alias a big_data, and don't change it
Мы избегаем копирования и мы говорим «эй, мы не будем изменять это». Это имеет другие побочные эффекты (например, временные переменные), но вам не стоит беспокоиться об этом сейчас.
Напротив, наша функция swap
не может быть const
, потому что мы действительно изменяем псевдонимы.
Надеюсь, это прояснит еще немного.
* Учебник по грубым указателям:
Указатель - это переменная, которая содержит адрес другой переменной. Например:
int i; // normal int
int* p; // points to an integer (is not an integer!)
p = &i; // &i means "address of i". p is pointing to i
*p = 2; // *p means "dereference p". that is, this goes to the int
// pointed to by p (i), and sets it to 2.
Итак, если вы видели функцию подкачки указатель-версия, мы передаем адрес переменных, которые мы хотим поменять, а затем делаем своп, разыменовывая, чтобы получить и установить значения.