Подтвердить что ты не робот

В чем же разница между "переходом по ссылке" на C и на С++?

Фраза "передать по ссылке" используется разработчиками C и С++, но они, как представляется, используются для обозначения разных вещей. В чем же разница между этой двусмысленной фразой на каждом языке?

4b9b3361

Ответ 1

Есть вопросы, которые уже касаются разницы между передачей по ссылке и передачей по значению. По сути, передача аргумента по значению функции означает, что функция будет иметь свою собственную копию аргумента - ее значение будет скопировано. Изменение этой копии не приведет к изменению исходного объекта. Однако при передаче по ссылке параметр внутри функции ссылается на тот же объект, который был передан - любые изменения внутри функции будут видны снаружи.

К сожалению, есть два способа использования фраз "передача по значению" и "передача по ссылке", которые могут вызвать путаницу. Я полагаю, что это отчасти объясняет, почему новые программисты 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++ перенял многие свои оригинальные языковые возможности из 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.