Что именно означает "пройти по ссылке"?

а кто имеет право решать?

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

вопрос о техническое значение термина pass-by-reference. (End edit)

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

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

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

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

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

Итак, если я все правильно понял, pass-by-reference

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

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

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 передает объекты по ссылке будет достаточно. Но согласно второму определению, pass-by-reference означает более или менее

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. правильно ли я понял приведенные выше определения pass-by-reference?
  2. есть другие определения?
  3. есть ли консенсус, какое определение является "правильным", если да, то какое?

8 ответов


конечно, разные люди в настоящее время имеют разные определения того, что означает "pass-by-reference". И именно поэтому они расходятся во мнениях о том, является ли что-то проходным или нет.

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

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

если согласиться с этой точкой зрения, то следует также рассмотреть большинство языков, в том числе такие разнообразные, как Python, Ruby, OCaml, Scheme, Smalltalk, SML, Go, JavaScript, Objective-C, etc. as pass-by-value только. Если что-то из этого кажется вам странным или противоречащим интуиции, я призываю вас указать, почему вы считаете, что семантика объектов в любом из этих языков отличается от объектов в 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 тоже.


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

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

  • pass by value: аргумент копируется в параметр при вводе подпрограммы
  • pass by result: параметр не определен при вводе подпрограммы и копируется в аргумент при возврате подпрограммы
  • pass by value-result: аргумент копируется в параметр при входе, а параметр копируется в аргумент при возврате
  • pass by reference: ссылка на переменную аргумента копируется в параметр; любой доступ переменной параметра прозрачно переводится в доступ переменной аргумента

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

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

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

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

теперь, в Java, технически у нас есть язык с pass-by-value, со значением переменной объекта, являющейся ссылкой на объект. Делает ли это Java pass-by-reference когда речь идет об объектных переменных, это вопрос вкуса.


оба ваших примера C фактически демонстрируют pass-by-value, потому что C не имеет pass-by-reference. Это просто значение, которое вы передаете указатель. Pass-by-reference происходит в таких языках, как Perl:

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

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

Java похож на C в этом уважайте, за исключением того, что в Java объекты являются "ссылочными типами", поэтому все, что у вас есть (и все, что вы можете передать), - это указатели на них. Что-то вроде этого:--8-->

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

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

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


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


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

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

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

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

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

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

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


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

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

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


самым очевидным тестом, который я могу придумать для реальной поддержки pass-by-reference, будет "тест подкачки". Язык, который изначально поддерживает передачу по ссылке, должен обеспечивать достаточную поддержку для записи функции 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, который разрешает копирование и переназначение-с использованием операторов присваивания языка и строгого равенства (включая любые перегрузки, указанные T); и
  2. не должно требовать, чтобы вызывающий преобразовывал или" обертывал " args (например: явно передавая указатель). Требование, чтобы args были отмечены как передаваемые по ссылке, нормально.

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

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


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

в оценке call-by-reference (также называемой pass-by-reference) функция получает неявную ссылку на переменную, используемую в качестве аргумента, а не копию ее значения. Это обычно означает, что функция может изменять (т. е. присваивать) переменную, используемую в качестве аргумента, - то, что будет видно ее вызывающему.

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

но этого достаточно для копирования, прочитайте подробное обсуждение (с примерами) в

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


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


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

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

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

эти определения используются в C# и Объект Паскаль которые оба имеют ключевые слова для передачи переменной по ссылке.

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


если вы знакомы с 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 был бы наиболее точно сопоставлен с указатели. Но в функция вызывает, сам указатель передается copy. ссылке указатель, как в #1j и #1c, изменяет исходную переменную, поэтому в этом смысле вы передаете ссылку на переменную. Однако сама переменная является только указателем, и она сама передается копией. Поэтому, когда вы назначаете ему что-то другое. как и в #2j и #2c, вы только восстанавливаете скопировать ссылки / указателя в локальной области f. Исходная переменная, a или w в соответствующих примерах, остается нетронутым.

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

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

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

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