Фраза «передача по ссылке» используется как разработчиками C, так и C ++, но, похоже, они означают разные вещи. В чем именно разница между этой двусмысленной фразой на каждом языке?
Есть вопросы, которые уже имеют дело с разница между передачей по ссылке и передачей по значению. По сути, передача аргумента по значению функции означает, что функция будет иметь свою собственную копию аргумента — ее значение копируется. Изменение этой копии не приведет к изменению исходного объекта. Однако при передаче по ссылке параметр внутри функции относится к тот же объект, который был передан — любые изменения внутри функции будут видны снаружи.
К сожалению, есть два способа использования фраз «передача по значению» и «передача по ссылке», которые могут вызвать путаницу. Я полагаю, что это отчасти объясняет, почему новые программисты на C ++ могут испытывать трудности с указателями и ссылками, особенно если они исходят из опыта работы с C.
В C все передается по значению в техническом смысле. То есть, что бы вы ни указали в качестве аргумента функции, оно будет скопировано в эту функцию. Например, вызов функции void foo(int)
с foo(x)
копирует значение x
в качестве параметра foo
, Это можно увидеть на простом примере:
void foo(int param) { param++; }
int main()
{
int x = 5;
foo(x);
printf("%d\n",x); // x == 5
}
Значение x
копируется в foo
и эта копия увеличивается. x
в main
продолжает иметь свое первоначальное значение.
Как я уверен, вы знаете, объекты могут быть указателя типа. Например, int* p
определяет p
в качестве указателя на int
, Важно отметить, что следующий код представляет два объекта:
int x = 5;
int* p = &x;
Первый тип int
и имеет значение 5
, Второй тип int*
и его значение является адресом первого объекта.
При передаче указателя на функцию вы все равно передаете его по значению. Содержащийся в нем адрес копируется в функцию. Изменение этого указатель внутри функции не будет меняться указатель вне функции — однако, изменяя объект, на который он указывает изменит объект вне функции. Но почему?
Поскольку два указателя, которые имеют одинаковое значение, всегда указывают на один и тот же объект (они содержат один и тот же адрес), объект, на который указывает указатель, может быть доступен и изменен через оба. Это дает семантику прохождения указанного объекта посредством ссылки, хотя никаких ссылок вообще не существовало — просто нет ссылок на C. Посмотрите на измененный пример:
void foo(int* param) { (*param)++; }
int main()
{
int x = 5;
foo(&x);
printf("%d\n",x); // x == 6
}
Можно сказать при прохождении int*
в функцию, что int
это указывает на то, что «передано по ссылке», но на самом деле int
фактически никогда нигде не передавался — в функцию был скопирован только указатель. Это дает нам разговорный1 значение «передать по значению» и «передать по ссылке».
Использование этой терминологии подтверждается терминами в стандарте. Когда у вас есть тип указателя, тип, на который он указывает, называется его ссылочный тип. То есть ссылочный тип int*
является int
,
тип указателя может быть получен из типа функции, типа объекта или неполного
типа, называется ссылочный тип.
Пока одинарный *
оператор (как в *p
) в стандарте называется косвенным обращением, обычно также называется разыменованием указателя. Это дополнительно продвигает понятие «передача по ссылке» в C.
C ++ перенял многие из своих оригинальных языковых возможностей из C. Среди них есть указатели, и поэтому эта разговорная форма «передачи по ссылке» все еще может использоваться — *p
все еще разыменование p
, Тем не менее, использование термина будет сбивать с толку, потому что C ++ вводит функцию, которой нет в C: способность действительно передавать Рекомендации.
Тип, за которым следует амперсанд, тип ссылки2. Например, int&
это ссылка на int
, при передаче аргумента функции, которая принимает ссылочный тип, объект действительно передается по ссылке. Здесь нет указателей, нет копирования объектов, нет ничего. Имя внутри функции на самом деле относится к тому же объекту, который был передан. В отличие от приведенного выше примера:
void foo(int& param) { param++; }
int main()
{
int x = 5;
foo(x);
std::cout << x << std::endl; // x == 6
}
Теперь foo
Функция имеет параметр, который является ссылкой на int
, Теперь при прохождении x
, param
относится к точно такому же объекту. Приращение param
имеет видимое изменение стоимости x
и сейчас x
имеет значение 6.
В этом примере ничего не было передано по значению. Ничего не было скопировано. В отличие от C, где передача по ссылке была просто передачей указателя по значению, в C ++ мы можем по-настоящему передать ссылку.
Из-за этой потенциальной неоднозначности в термине «передача по ссылке» лучше использовать его только в контексте C ++, когда вы используете ссылочный тип. Если вы передаете указатель, вы не передаете по ссылке, вы передаете указатель по значению (то есть, конечно, если вы не передаете ссылку на указатель! Например, int*&
). Однако вы можете столкнуться с использованием «передачи по ссылке», когда используются указатели, но теперь, по крайней мере, вы знаете, что на самом деле происходит.
Другие языки программирования еще больше усложняют ситуацию. В некоторых, таких как Java, каждая ваша переменная известна как ссылка на объект (не то же самое, что ссылка в C ++, больше похоже на указатель), но эти ссылки передаются по значению. Таким образом, даже если вы, кажется, передаете функцию по ссылке, вы фактически копируете ссылку в функцию по значению. Это тонкое отличие от передачи по ссылке в C ++ замечается при назначении нового объекта для ссылки, переданной в:
public void foo(Bar param) {
param.something();
param = new Bar();
}
Если бы вы вызывали эту функцию в Java, передавая некоторый объект типа Bar
Призыв к param.something()
будет вызван для того же объекта, который вы передали. Это потому, что вы передали ссылку на ваш объект. Тем не менее, хотя новый Bar
назначен на param
объект вне функции — это все тот же старый объект. Новый никогда не виден снаружи. Это потому, что ссылка внутри foo
переназначается на новый объект. Этот вид переназначения ссылок невозможен при использовании ссылок C ++.
1 Говоря «разговорный», я не имею в виду, что значение «передача по ссылке» в C является менее правдивым, чем значение в C ++, просто то, что C ++ действительно имеет ссылочные типы, и поэтому вы действительно проходите мимо ссылка. Значение C — это абстракция над тем, что действительно передается по значению.
2 Конечно, это ссылки на lvalue, и теперь у нас есть ссылки на rvalue в C ++ 11.
Других решений пока нет …