Что именно означает «передать по ссылке»? - PullRequest
15 голосов
/ 13 ноября 2011

А кто имеет полномочия решать?

Редактировать: По-видимому, мне не удалось правильно сформулировать свой вопрос.
Я не спрашиваю, как работает передача аргументов Java. Я знаю, что то, что выглядит как переменная, содержащая объект, на самом деле является переменной, содержащей ссылку на объект, и эта ссылка передается по значению. Есть много прекрасных объяснений этого механизма здесь (в связанных потоках и других) и в других местах.

Вопрос касается технического значения термина pass-by-reference. (Конец редактирования)

Я не уверен, что это правильный вопрос для SO, извините, если нет, но я не знаю лучшего места. Многое уже было сказано в других вопросах здесь, например Является ли Java «передачей по ссылке» или «передачей по значению»? и передача по ссылке или передача по значению? , но я не нашел авторитетного ответа на вопрос, что означает этот термин.

Я думал, что «передать по ссылке» означает «передать ссылку (обычно указатель) на объект», чтобы вызываемый объект мог изменить объект, который видит вызывающий объект, тогда как «передать по значению» означает копирование объекта, и позволить вызываемому пользователю повеселиться с копией (очевидная проблема: что, если объект содержит ссылки, глубокое копирование или мелкое).
Пой, что FW поворачивается вверх лотов из мест Говоря"передать по ссылке" означает только то, здесь некоторый аргумент, что это означает больше, но определение все еще читает

Режим ParameterPassing, в котором ссылка (или, если вы хотите быть политически некорректным, указатель) на фактический параметр передается в формальный параметр; когда вызываемый объект нуждается в формальном параметре, он разыменовывает указатель для его получения.

Я не нашел много мест, дающих более четкое определение термина, на этой странице я обнаружил, что «lvalue формального параметра установлено в lvalue фактического параметра». и, если я правильно понимаю, то же определение используется здесь («Формальный параметр просто действует как псевдоним для фактического параметра.»)

Фактически, единственные места, которые я нашел, где используется более строгое определение, - это места, которые противоречат идее, что в Java объекты передаются по ссылке (это может быть связано с отсутствием у меня google-fu).

Итак, если я все понял, переход по ссылке

class Thing { ... }
void byReference(Thing object){ ... }
Thing something;
byReference(something);

согласно первому определению примерно соответствует (в С)

struct RawThing { ... };
typedef RawThing *Thing;
void byReference(Thing object){
    // do something
}
// ...
struct RawThing whatever = blah();
Thing something = &whatever;
byReference(something); // pass whatever by reference
// we can change the value of what something (the reference to whatever) points to, but not
// where something points to

и в этом смысле достаточно сказать, что Java передает объекты по ссылке. Но согласно второму определению, передача по ссылке означает более или менее

struct RawThing { ... };
typedef RawThing *RawThingPtr;
typedef RawThingPtr *Thing;
void byReference(Thing object){
    // do something
}
// ...
RawThing whatever = blah();
RawThingPtr thing_pointer = &whatever;
byReference(&thing_pointer); // pass whatever by reference
// now we can not only change the pointed-to (referred) value,
// but also where thing_pointer points to

И поскольку Java позволяет вам иметь только указатели на объекты (ограничивая то, что вы можете с ними делать), но не имеет указателей на указатели, в этом смысле говорить, что Java передает объекты по ссылке, совершенно неверно.

Итак,

  1. Правильно ли я понял приведенные выше определения передачи по ссылке?
  2. Существуют ли другие определения вокруг?
  3. Есть ли согласие, какое определение является "правильным", если да, то какое?

Ответы [ 8 ]

8 голосов
/ 14 ноября 2011

Конечно, разные люди в настоящее время имеют разные определения того, что означает «передача по ссылке». И именно поэтому они не согласны с тем, является ли что-то ссылкой или нет.

Однако, какое бы определение вы ни использовали, вы должны использовать его последовательно для разных языков. Вы не можете сказать, что один язык имеет передачу по значению, и имеет ту же семантику в другом языке, и сказать, что это передача по ссылке. Указание на аналогии между языками - лучший способ разрешить этот спор, потому что, хотя у людей могут быть сильные мнения о способах прохождения в определенных языках, когда вы противопоставляете идентичную семантику другим языкам, иногда это приводит к противоречивым результатам, которые вынуждают их переосмыслить свое определение.

  • Одним из основных представлений является то, что Java передается только по значению. (Выполняйте поиск по всему Интернету, и вы найдете эту точку зрения.) Эта точка зрения состоит в том, что объекты не являются значениями, а всегда обрабатываются с помощью ссылок, и, следовательно, это ссылки, которые присваиваются или передаются по значению. В этом представлении утверждается, что проверкой передачи по ссылке является возможность присвоения переменной в области вызова.

Если согласен с этой точкой зрения, то следует также учитывать большинство языков, в том числе и такие, как Python, Ruby, OCaml, Scheme, Smalltalk, SML, Go, JavaScript, Objective-C и т. Д. . как только для передачи по значению . Если что-то из этого кажется вам странным или нелогичным, я призываю вас указать, почему вы считаете, что семантика объектов в любом из этих языков отличается от объектов в Java. (Я знаю, что некоторые из этих языков могут в явном виде утверждать, что они передаются по ссылке; но это не имеет значения, что они говорят; непротиворечивое определение должно применяться ко всем языкам на основе фактического поведения.)

  • Если вы придерживаетесь противоположного мнения, что объекты в Java передаются по ссылке, то вы также должны рассматривать C как передачу по ссылке.

Возьмите пример с Java:

class Thing { int x; }
void func(Thing object){ object.x = 42; object = null; }
Thing something = null;
something = new Thing();
func(something);

в C, это будет эквивалентно этому:

typedef struct { int x; } Thing;
void func(Thing *object){ object->x = 42; object = NULL; }
Thing *something = NULL;
something = malloc(sizeof Thing);
memset(something, 0, sizeof(something));
func(something);
// later:
free(something);

Я утверждаю, что вышесказанное семантически эквивалентно; отличается только синтаксис. Единственные синтаксические различия:

  1. C требует явного * для обозначения типа указателя; Ссылочные типы Java (указатели на объекты) не нуждаются в явном *.
  2. C использует -> для доступа к полю через указатель; Java просто использует .
  3. Java использует new для динамического выделения памяти для нового объекта в куче; C использует malloc для его выделения, а затем нам нужно инициализировать память.
  4. В Java есть сборка мусора

Обратите внимание, что, что важно,

  1. Синтаксис для вызова функции с объектом одинаков в обоих случаях: func(something), без необходимости делать что-либо вроде ввода адреса или чего-либо еще.
  2. В обоих случаях объект распределяется динамически (он может находиться за пределами функции). И
  3. В обоих случаях object = null; внутри функции не влияет на область вызова.

Так что семантика в обоих случаях одинакова, поэтому, если вы вызываете Java pass-by-reference, вы должны также вызывать C pass-by-reference.

5 голосов
/ 13 ноября 2011

Кто имеет право решать? Никто и все. Вы сами решаете; писатель выбирает свою книгу; и читатель решает, согласиться ли с автором.

Чтобы понять термин, нужно пойти вглубь языка (и объяснение их в терминах кода на C, скорее, упускает из виду). Стили передачи параметров относятся к механизмам, которые компиляторы обычно используют для создания определенного поведения. Обычно определяется следующее:

  • передача по значению: аргумент копируется в параметр при вводе подпрограммы
  • передача по результату: параметр не определен при вводе подпрограммы и копируется в аргумент при возврате подпрограммы
  • передача по значению-результату: аргумент копируется в параметр при вводе, а параметр копируется в аргумент при возвращении
  • передать по ссылке: ссылка на переменную аргумента копируется в параметр; любой доступ к переменной параметра прозрачно переводится в доступ к переменной аргумента

(Примечание к терминологии: параметр - это переменная, определенная в подпрограмме, аргумент - это выражение, которое используется в вызове.)

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

Важность стиля передачи параметров заключается в его эффекте: при передаче по значению любые изменения, внесенные в параметр, не передаются в аргумент; при передаче по результату любые изменения, внесенные в параметр, передаются аргументу в конце; при передаче по ссылке любые изменения, внесенные в параметр, передаются в аргумент по мере их внесения.

Некоторые языки определяют более одного стиля передачи, что позволяет программисту выбирать предпочтительный стиль для каждого параметра в отдельности. Например, в Pascal стиль по умолчанию - передача по значению, но программист может использовать ключевое слово var, чтобы указать передачу по ссылке. Некоторые другие языки определяют один стиль передачи. Существуют также языки, которые определяют разные стили для разных типов (например, в C передача по значению используется по умолчанию, но массивы передаются по ссылке).

Теперь в Java технически у нас есть язык с передачей по значению, причем значение переменной объекта является ссылкой на объект. Делает ли это передачу Java по ссылке в отношении переменных объекта - дело вкуса.

5 голосов
/ 13 ноября 2011

Оба ваших примера C на самом деле демонстрируют передачу по значению, потому что C не имеет передачи по ссылке. Просто передаваемое вами значение является указателем. Передача по ссылке происходит в таких языках, как Perl:

sub set_to_one($)
{
    $_[0] = 1; # set argument = 1
}
my $a = 0;
set_to_one($a); # equivalent to: $a = 1

Здесь переменная $a фактически передается по ссылке, поэтому подпрограмма может изменить ее. Это не изменение какого-либо объекта, на который $a указывает через косвенное обращение; скорее он сам изменяет $a.

В этом отношении Java похожа на C, за исключением того, что в Java объекты являются «ссылочными типами», поэтому все, что у вас когда-либо было (и все, что вы можете когда-либо передавать), - это указатели на них. Примерно так:

void setToOne(Integer i)
{
    i = 1; // set argument = 1
}

void foo()
{
    Integer a = 0;
    setToOne(a); // has no effect
}

на самом деле не изменится a; это только переназначает i.

2 голосов
/ 23 мая 2013

Передача по ссылке, по сути, передача ссылки на значение - а не его копия - в качестве аргумента.


Iдумаю, прежде чем мы продолжим, некоторые вещи должны быть определены.Я могу использовать их иначе, чем вы привыкли видеть их использованными.

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

  • A ссылка - псевдонимили обращаться к объекту.На уровне языка ссылка в основном действует как то, на что она ссылается;в зависимости от языка, компилятор / интерпретатор / среда выполнения / gnomes автоматически разыменовывает , когда нужен фактический объект.

  • A значение является результатом оценки выражения.Это конкретный объект, который можно хранить, передавать в функции и т. Д. (ООП отмечает, обратите внимание, что здесь я использую «объект» в общем смысле «молекула данных», а не ООП «экземпляр класса»).)

  • A переменная является именованной ссылкой на предварительно выделенное значение .

    Особое примечание: переменные не являются значениями. Несмотря на имя, переменные обычно не меняются.Их значение - вот что меняется.То, что их так легко перепутать, отчасти свидетельствует о том, насколько хороша иллюзия референта <->.

  • A переменная с типом ссылки (а-ля Java, C #, ...) - это переменная , значение которой равно ссылка .


Большинствоязыки, когда вы передаете переменную в качестве аргумента, по умолчанию создают копию значения переменной и передают копию.Вызываемый связывает свое имя для параметра с этой копией.Это называется «передачей по значению» (или, точнее, «передачей по копии»).Две переменные на каждой стороне вызова имеют разные места хранения и, таким образом, являются совершенно разными переменными (связаны только с тем, что они обычно начинаются с одинаковых значений).

Передача по ссылке, с другой стороныне делает копию.Вместо этого он передает саму переменную (без имени).То есть он передает ссылку на то же значение, что и псевдонимы переменной.(Обычно это делается путем неявной передачи указателя на хранилище переменной, но это просто деталь реализации; вызывающий и вызываемый не должны знать или заботиться о том, как это происходит.) Вызываемый связывает имя своего параметра с этим местоположением.Конечный результат заключается в том, что обе стороны используют одно и то же место хранения (просто под разными именами).Любые изменения, которые вызываемый пользователь вносит в свою переменную, также вносятся в переменную вызывающего.Например, в случае объектно-ориентированных языков переменной можно присвоить совершенно другое значение.

Большинство языков (включая Java) не поддерживают это изначально.О, им нравится говорить они делают ... но это потому, что люди, которые никогда не смогли по-настоящему пройти по ссылке , часто не замечают тонкой разницы между этим и передача ссылки по значению .Где путаница приходит с этими языками, с переменными ссылочного типа.Сама Java никогда не работает напрямую с объектами ссылочного типа, но с ссылками на эти объекты.Разница заключается в том, что переменные «содержат» указанные объекты.Значение переменной ссылочного типа является такой ссылкой (или, иногда, специальным ссылочным значением, которое означает «ничто»).Когда Java передает такую ​​ссылку, хотя она не копирует объект, она все равно копирует значение (то есть: ссылка, которую получает функция, является копией значения, на которое ссылается переменная).То есть, передает ссылку , но передает ее по значению .Это позволяет большинство вещей, которые допускает передача по ссылке, , но не все .


Самым очевидным тестом, который я могу придумать для реальной поддержки передачи по ссылке, будет "тест подкачки".Язык, который изначально поддерживает передачу по ссылке, должен обеспечить достаточную поддержку для написания функции swap, которая меняет значения своих аргументов.Код, эквивалентный следующему:

swap (x, y):       <-- these should be declared as "reference to T"
  temp = x
  x = y
  y = temp

--

value1 = (any valid T)
value2 = (any other valid T)

a = value1
b = value2
swap(a, b)
assert (a == value2 and b == value1)
  1. должен быть возможен и успешно выполняться - для любого типа T, который допускает копирование и переназначение - с использованием операторов присвоения языка и строгого равенства (включая любые перегрузки, указанные вТ);и
  2. не должен требовать от вызывающей стороны преобразовывать или «оборачивать» аргументы (например, явно передавая указатель).Требовать, чтобы аргументы были помечены как переданные по ссылке, это нормально.

(Очевидно, что языки, у которых нет изменяемых переменных, не могут быть протестированы таким образом, но это нормально, потому что они не имеют значения . Большое семантическое различие между ними заключается в том, насколько изменяемой является переменная вызывающего для вызываемого. Когда значение переменной в любом случае не модифицируемо, разница становится просто деталью реализации или оптимизацией.)

Обратите внимание, большая часть разговоров в этом ответе посвящена "переменным".Ряд языков, таких как C ++, также позволяют передавать анонимные значения по ссылке.Механизм такой же;значение занимает память, а ссылка является псевдонимом для него.Это просто не обязательно иметь имя в вызывающей стороне.

1 голос
/ 13 ноября 2011

Википедия дает очень четкое определение по ссылке, которое я не могу улучшить:

При оценке по ссылке (также называемой передачей по ссылке) функция получает неявную ссылку на переменную, используемую в качестве аргумента, а не копию ее значения. Как правило, это означает, что функция может изменять (то есть присваивать) переменную, используемую в качестве аргумента, что будет видно ее вызывающей стороне.

Обратите внимание, что ни один из ваших примеров не является вызовом по ссылке, потому что назначение формального параметра в C никогда не изменяет аргумент, видимый вызывающей стороной.

Но этого достаточно, чтобы вставить копию, прочитайте подробное обсуждение (с примерами) на

http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference

1 голос
/ 13 ноября 2011

Java не передается по ссылке. Вы всегда передаете копию / по значению. Однако, если вы передадите объект, вы получите копию ссылки. Таким образом, вы можете напрямую редактировать объект, однако, если вы перезапишете локальную ссылку, исходная ссылка на объект не будет переопределена.

0 голосов
/ 13 ноября 2011

Если вы знакомы с C, возможно, следующая аналогия объясняет, как работает Java.Это будет верно только для объектов типа класса (а не фундаментального типа).

В Java мы можем иметь переменную и передать ее функции:

void f(Object x)
{
  x.bar = 5;    // #1j
  x = new Foo;  // #2j
}

void main()
{
  Foo a;
  a.bar = 4;
  f(a);
  // now a.bar == 5
}

В Cэто будет выглядеть следующим образом:

void f(struct Foo * p)
{
  p->bar = 5;                      // #1c
  p = malloc(sizeof(struct Foo));  // #2c
}

int main()
{
  struct Foo * w = malloc(sizeof(struct Foo));
  w->bar = 4;
  f(w);
  /* now w->bar = 5; */
}

В Java переменные типа класса всегда ссылки , которые в C будут наиболее точно отображаться в указатели ,Но в вызовах функций сам указатель передается копией. Доступ к указателю, как в # 1j и # 1c, изменяет исходную переменную, поэтому в этом смысле вы передаете ссылку на переменную.Однако сама переменная является только указателем, а сама она передается копией.Поэтому, когда вы назначаете что-то еще на это.как в # 2j и # 2c, вы только повторно связываете копию ссылки / указателя в локальной области действия f.Исходная переменная a или w в соответствующих примерах остается неизменной.

Короче говоря: все является ссылкой, а ссылки передаются по значению.

В C, нас другой стороны, я мог бы реализовать «передачу по ссылке», объявив void v(struct Foo ** r); и вызвав f(&w);это позволило бы мне изменить w изнутри f.

Примечание 1. Это не относится к фундаментальным типам, таким как int, которые полностью передаются по значению.

Примечание 2: пример C ++ был бы немного более аккуратным, поскольку я мог передать указатель по ссылке (и мне не нужно было говорить struct): void f(Foo * & r) { r = new Foo; } и f(w);.

0 голосов
/ 13 ноября 2011

Передача параметров по ссылке означает, что вложенность указателя параметров глубже, чем вложенность указателя локальных переменных. Если у вас есть переменная с типом класса, переменная является указателем на фактическое значение. Переменная типа примитива содержит само значение.

Теперь, если вы передаете эти переменные по значению, вы сохраняете вложенность указателя: ссылка на объект остается указателем на объект, а примитивная переменная сохраняет само значение.

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

Эти определения используются в C # и Object Pascal , которые оба имеют ключевые слова для передачи переменной по ссылке.

Чтобы ответить на ваш вопрос: поскольку последние переменные - whatever в первом примере и thing_pointer во втором - передаются в функцию через указатель (&), обе передаются по ссылке.

...