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

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

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

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

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

Я подумал, что "передать по ссылке" означает "передать ссылку (обычно указатель) на объект", поэтому вызывающий может изменить объект, который видит вызывающий, а "передать по значению" означает копирование объекта и позволяя вызываемому веселиться с копией (очевидная проблема: что, если объект содержит ссылки, глубокие копии или мелкие).
Sing the FW появляется lots места say "pass by reference" означает именно это, здесь есть некоторые аргументы, что это означает больше, но определение все еще читает

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

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

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

Итак, если у меня все получится, перейдите по ссылке

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 передает объекты по ссылке полностью неверно.

Итак,

  • Я правильно понял приведенные выше определения pass-by-reference?
  • Существуют ли другие определения?
  • Существует ли консенсус, какое определение является "правильным", если да, то какое?

Ответ 1

Конечно, разные люди в настоящее время имеют разные определения того, что означает "передача по ссылке". Вот почему они не согласны с тем, что-то передается по ссылке или нет.

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

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

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

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

Возьмите пример 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);

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

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

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

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

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

Ответ 2

Оба ваших примера 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.

Ответ 3

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

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

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

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

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

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

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

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

Ответ 4

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


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

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

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

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

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

    Особенно примечание: переменные не являются значениями. Несмотря на это, переменные обычно не меняются. Их ценность - это то, что меняется. То, что они так легко смешиваются, частично является свидетельством того, насколько хороша эталонная иллюзия ссылок - lt; → .

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


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

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

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

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

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

Ответ 5

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

Ответ 6

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

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

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

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

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

Ответ 7

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

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

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

Перейдите по ссылке:

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

В отличие от передачи по значению, передача по ссылке гарантирует, что изменение, внесенное в значение в другой области, сохраняется при выходе этой области. Например, если мы передаем один аргумент в метод по ссылке, а метод присваивает этому значению внутри своего тела, назначение сохраняется при выходе метода. Это может быть продемонстрировано с использованием следующего фрагмента кода C++:

   ?#include <iostream>

using namespace std;

void process(int& value) 

{

cout << "Value passed into function: " << value << endl;

value = 10;

cout << "Value before leaving function: " << value << endl;

}

int main()

 {

int someValue = 7;

 cout << "Value before function call: " << someValue << endl;

process(someValue);

 cout << "Value after function call: " << someValue << endl;

 return 0;

}