В чем разница между передачей по ссылке и передачей по значению? - PullRequest
512 голосов
/ 17 декабря 2008

В чем разница между

  1. параметр, переданный по ссылке
  2. параметр, передаваемый по значению?

Не могли бы вы привести несколько примеров, пожалуйста?

Ответы [ 15 ]

1016 голосов
/ 10 января 2009

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

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

Вторым источником путаницы является тот факт, что в «передаче по ссылке», «ссылка» имеет более узкое значение, чем общий термин «ссылка» (потому что фраза предшествует этому).


Теперь, подлинное определение:

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

  • Когда параметр передается по значению , вызывающий и вызываемый абоненты имеют две независимые переменные с одинаковым значением. Если вызываемый объект изменяет переменную параметра, эффект не будет виден вызывающему.

Что следует отметить в этом определении:

  • «Переменная» здесь означает саму переменную вызывающего (локальную или глобальную) - т.е. если я передам локальную переменную по ссылке и присвоу ей, я изменю саму переменную вызывающего не например на что бы он ни указывал, если это указатель.

    • Теперь это считается плохой практикой (как неявная зависимость). Таким образом, практически все более новые языки являются исключительно или почти исключительно передачей по значению. Передача по ссылке теперь в основном используется в форме "аргументов вывода / ввода" в языках, где функция не может вернуть более одного значения.
  • Значение слова «ссылка» в «передать по ссылке» . Разница с общим термином «ссылка» заключается в том, что эта «ссылка» является временной и неявной. То, что в основном получает вызываемый объект, является «переменной», которая каким-то образом «совпадает» с оригиналом one. То, как конкретно этот эффект достигается, не имеет значения (например, язык может также раскрывать некоторые детали реализации - адреса, указатели, разыменование - все это не имеет значения; если это чистый эффект, то это переход по ссылке ).


Теперь в современных языках переменные, как правило, имеют «ссылочные типы» (другая концепция, изобретенная позже, чем «передача по ссылке» и вдохновленная ею), то есть фактические данные объекта хранятся где-то отдельно (обычно в куче), и только «ссылки» на него когда-либо хранятся в переменных и передаются как параметры. 3

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

  • Если ссылка просто берется из переменной вызывающего и передается в качестве аргумента, это имеет тот же эффект, что и передача по ссылке: если в вызываемом объекте мутирован , вызывающий будет увидеть изменения.
    • Однако, если переменная, содержащая эту ссылку, переназначена, перестанет указывать на этот объект, поэтому любые дальнейшие операции с этой переменной будут вместо этого влиять на то, на что она указывает сейчас.
  • Чтобы иметь тот же эффект, что и при передаче по значению, в какой-то момент делается копия объекта. Варианты включают в себя:
    • Вызывающий абонент может просто сделать личную копию перед вызовом и вместо этого дать вызываемому ссылку на него.
    • В некоторых языках некоторые типы объектов являются «неизменяемыми»: любая операция над ними, которая, кажется, изменяет значение, фактически создает совершенно новый объект, не затрагивая исходный. Таким образом, передача объекта такого типа в качестве аргумента всегда имеет эффект передачи по значению: копия для вызываемой стороны будет сделана автоматически, если и когда она потребует изменения, и объект вызывающей стороны никогда не будет затронут.
      • На функциональных языках все объекты являются неизменяемыми.

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

Для них нет согласованного имени, что приводит к искаженным объяснениям, таким как "вызов по значению, где значение является ссылкой". В 1975 году Барбара Лисков предложила термин « вызов по разделу объекта » (или иногда просто «вызов по обмену»), хотя он так и не получил широкого распространения. Более того, ни одна из этих фраз не проводит параллели с оригинальной парой. Неудивительно, что старые термины в конечном итоге использовались в отсутствие чего-то лучшего, что приводило к путанице. 4


ПРИМЕЧАНИЕ : Этот ответ долгое время говорил:

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

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

Это в основном правильно за исключением более узкого значения "ссылки" - оно является временным и неявным (это не обязательно, но оно явное и / или постоянное) являются дополнительными функциями, а не частью семантики передачи по ссылке, как объяснено выше). Более близкая аналогия - дать вам копию документа и пригласить вас поработать над оригиналом.


1 Если вы не программируете на Fortran или Visual Basic, это не стандартное поведение, и в большинстве современных языков использование истинного вызова по ссылке даже невозможно.

2 Изрядное количество старших тоже поддерживают его

3 В некоторых современных языках все типы являются ссылочными типами. Этот подход был впервые введен языком CLU в 1975 году и с тех пор был принят многими другими языками, включая Python и Ruby. И во многих других языках используется гибридный подход, где некоторые типы являются «типами значений», а другие - «ссылочными типами» - среди них C #, Java и JavaScript.

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

133 голосов
/ 17 декабря 2008

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

  • Java поддерживает только передачу по значению. Всегда копирует аргументы, даже если при копировании ссылки на объект параметр в вызываемой функции будет указывать на тот же объект, а изменения этого объекта будут видны в вызывающей программе. Поскольку это может сбивать с толку, здесь - это то, что Джон Скит говорит об этом.
  • C # поддерживает передачу по значению и передачу по ссылке (ключевое слово ref используется в вызывающей и вызываемой функции). У Джона Скита также есть хорошее объяснение этого здесь .
  • C ++ поддерживает передачу по значению и передачу по ссылке (тип ссылочного параметра, используемый в вызываемой функции). Вы найдете объяснение этому ниже.

Коды

Так как мой язык - C ++, я буду использовать его здесь

// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
    p = NULL;
}

// passes an integer
void call_by_value(int p) { // :2
    p = 42;
}

// passes an integer by reference
void call_by_reference(int & p) { // :3
    p = 42;
}

// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
    *p = 10; // changes what p points to ("what p references" in java)
    // only changes the value of the parameter, but *not* of 
    // the argument passed by the caller. thus it's pass-by-value:
    p = NULL;
}

int main() {
    int value = 10;
    int * pointer = &value;

    call_by_value(pointer); // :1
    assert(pointer == &value); // pointer was copied

    call_by_value(value); // :2
    assert(value == 10); // value was copied

    call_by_reference(value); // :3
    assert(value == 42); // value was passed by reference

    call_by_value_special(pointer); // :4
    // pointer was copied but what pointer references was changed.
    assert(value == 10 && pointer == &value);
}

И пример на Java не повредит:

class Example {
    int value = 0;

    // similar to :4 case in the c++ example
    static void accept_reference(Example e) { // :1
        e.value++; // will change the referenced object
        e = null; // will only change the parameter
    }

    // similar to the :2 case in the c++ example
    static void accept_primitive(int v) { // :2
        v++; // will only change the parameter
    }        

    public static void main(String... args) {
        int value = 0;
        Example ref = new Example(); // reference

        // note what we pass is the reference, not the object. we can't 
        // pass objects. The reference is copied (pass-by-value).
        accept_reference(ref); // :1
        assert ref != null && ref.value == 1;

        // the primitive int variable is copied
        accept_primitive(value); // :2
        assert value == 0;
    }
}

Википедия

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value

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

Этот парень в значительной степени прибивает это:

http://javadude.com/articles/passbyvalue.htm

66 голосов
/ 24 января 2016

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

TL; DR

Проще говоря:

  • вызов по значению означает, что вы передаете значения в качестве аргументов функции
  • вызов по ссылке означает, что вы передаете переменные в качестве аргументов функции

В переносном смысле:

  • Вызов по значению - это где Я записываю что-то на лист бумаги и передаю вам . Может быть, это URL, может быть, это полная копия «Войны и мира». Неважно, что это, это на листе бумаги, который я вам дал, и теперь он фактически ваш лист бумаги . Теперь вы можете делать наброски на этом листе бумаги или использовать этот листок бумаги, чтобы найти что-то еще и поиграть с ним, что угодно.
  • Звоните по ссылке , когда я даю вам свой блокнот, в котором что-то записано . Вы можете писать в моей записной книжке (может быть, я этого хочу, а может и нет), а потом я сохраняю записную книжку с теми каракулями, которые вы там положили. Кроме того, если то, что вы или я написали, содержит информацию о том, как найти что-то где-то еще, вы или я можете пойти туда и поиграть с этой информацией.

Что означает «звонок по значению» и «звонок по ссылке» не означает

Обратите внимание, что обе эти концепции полностью независимы и ортогональны от концепции ссылочных типов (которые в Java являются всеми типами, которые являются подтипами Object, а в C # все class типы), или концепция типов указателей , как в C (которые семантически эквивалентны "ссылочным типам" Java, просто с другим синтаксисом).

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

Обратите внимание, что в C ++ есть понятие «ссылки» (например, int&), которое не подобно «ссылочным типам» Java и C #, но - это like «вызов по ссылке ». «Ссылочные типы» в Java и C # и все типы в Python похожи на то, что в C и C ++ называют «типами указателей» (например, int*).


Хорошо, вот более длинное и более формальное объяснение.

Терминология

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

Для начала, вот пример на некотором C-подобном языке объявления функции:

void foo(int param) {  // line 1
  param += 1;
}

А вот пример вызова этой функции:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

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

  • foo - это функция , объявленная в строке 1 (Java настаивает на создании всех функций-методов, но концепция одинакова без потери общности; C и C ++ проводят различие между объявлением и определением, которое Я не буду вдаваться в подробности)
  • param - это формальный параметр до foo, также объявленный в строке 1
  • arg - это переменная , а именно локальная переменная функции bar, объявленная и инициализированная в строке 2
  • arg также является аргументом для конкретного вызова из foo в строке 3

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

  • A значение является результатом вычисления выражения в языке. Например, в приведенной выше функции bar после строки int arg = 1; выражение arg имеет значение 1.
  • A переменная - это контейнер для значений . Переменная может быть изменяемой (это значение по умолчанию в большинстве C-подобных языков), доступна только для чтения (например, объявлена ​​с использованием final в Java или * # 1149 *) или может быть неизменной (например, с использованием const в C ++).

Другая важная пара понятий, которую следует различать: параметр против аргумент :

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

Вызов по значению

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

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

int arg = 1;
int another_variable = arg;

Здесь arg и another_variable являются полностью независимыми переменными - их значения могут изменяться независимо друг от друга. Однако в точке, где объявлено another_variable, оно инициализируется так, чтобы оно содержало то же значение, что и arg, то есть 1.

Поскольку они являются независимыми переменными, изменения в another_variable не влияют на arg:

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

Это точно так же, как отношения между arg и param в нашем примере выше, который я повторю здесь для симметрии:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

Это точно, как если бы мы написали код таким образом:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

То есть определяющим признаком того, что вызывает по значению , является то, что вызываемый абонент (foo в данном случае) получает значения в качестве аргументов, но имеет свой отдельный переменные для этих значений из переменных вызывающего абонента (bar в данном случае).

Возвращаясь к моей метафоре выше, если я bar, а вы foo, когда я вам звоню, я вручаю вам лист бумаги с написанным значением . Вы называете этот листок бумаги param. Это значение является копией значения, которое я записал в своей записной книжке (мои локальные переменные), в переменную, которую я называю arg.

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

Звоните по ссылке

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

Возвращаясь к нашему примеру выше, это эквивалентно:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here

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

Очень немногие языки поддерживают вызов по ссылке, но C ++ может сделать это так:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

В этом случае param не просто имеет то же значение , что и arg, на самом деле равно arg (просто под другим именем) и т. Д. bar может наблюдать, что arg был увеличен.

Обратите внимание, что это , а не , как работает любой из Java, JavaScript, C, Objective-C, Python или почти любой другой популярный язык сегодня. Это означает, что эти языки не вызывают по ссылке, они называют по значению.

Приложение: вызов с помощью общего доступа к объекту

Если у вас есть вызов по значению , но фактическим значением является ссылочный тип или тип указателя , то само "значение" не является " Это очень интересно (например, в C это просто целое число, зависящее от платформы) - интересно то, что это значение указывает на .

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

Снова заимствуя аналогию с URL, тот факт, что я дал вам копию URL-адреса веб-сайта, не особенно интересен, если мы оба заботимся о веб-сайте, а не об URL-адресе. , Тот факт, что вы перебираете свою копию URL-адреса, не влияет на мою копию URL-адреса, нас не волнует (и на самом деле, в таких языках, как Java и Python, «URL», или значение ссылочного типа, может не может быть изменено, может только та вещь, на которую он указывает).

Барбара Лисков, когда она изобрела язык программирования CLU (который имел эту семантику), поняла, что существующие термины «вызов по значению» и «вызов по ссылке» не особенно полезны для описания семантики этого нового языка. Поэтому она изобрела новый термин: вызов по совместному использованию объекта .

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

55 голосов
/ 24 марта 2016

Прежде чем понять 2 термина, вы ДОЛЖНЫ понять следующее. У каждого объекта есть две вещи, которые могут его выделить.

  • Его значение.
  • Его адрес.

Так что, если вы скажете employee.name = "John"

знаю, что в name есть 2 вещи. Его значение, равное "John", а также его местоположение в памяти, представляющее собой шестнадцатеричное число, может быть таким: 0x7fd5d258dd00

В зависимости от архитектуры языка или типа (класса, структуры и т. Д.) Вашего объекта, вы можете передавать "John" или 0x7fd5d258dd00

Передача "John" называется передачей по значению. Передача 0x7fd5d258dd00 известна как передача по ссылке. Любой, кто указывает на эту область памяти, будет иметь доступ к значению "John".

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

52 голосов
/ 17 декабря 2008

Вот пример:

#include <iostream>

void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }

int main()
{
    int x = 0;
    by_val(x); std::cout << x << std::endl;  // prints 0
    by_ref(x); std::cout << x << std::endl;  // prints 2

    int y = 0;
    by_ref(y); std::cout << y << std::endl;  // prints 2
    by_val(y); std::cout << y << std::endl;  // prints 2
}
25 голосов
/ 12 ноября 2014

Самый простой способ получить это - файл Excel. Допустим, например, что у вас есть два числа, 5 и 2 в ячейках A1 и B1 соответственно, и вы хотите найти их сумму в третьей ячейке, скажем, A2. Вы можете сделать это двумя способами.

  • Либо , передавая их значения в ячейку A2 , введя = 5 + 2 в эту ячейку. В этом случае, если значения ячеек A1 или B1 изменяются, сумма в A2 остается неизменной.

  • Или , передавая «ссылки» ячеек A1 и B1 в ячейку A2 , набрав = A1 + B1 . В этом случае, если значения ячеек A1 или B1 изменяются, изменяется и сумма в A2.

18 голосов
/ 17 декабря 2008

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

11 голосов
/ 17 декабря 2008

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

6 голосов
/ 28 декабря 2015

Передача по значению - функция копирует переменную и работает с копией (поэтому она ничего не меняет в исходной переменной)

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

Пример (скопируйте и используйте / попробуйте сами и посмотрите):

#include <iostream>

using namespace std;

void funct1(int a){ //pass-by-value
    a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
    a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}

int main()
{
    int a = 5;

    funct1(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
    funct2(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7

    return 0;
}

Сохраняй это простым, выглядывает. Стены текста могут быть вредной привычкой.

5 голосов
/ 17 декабря 2008

Основное различие между ними заключается в том, что переменные типа значения хранят значения, поэтому при указании переменной типа значения в вызове метода копия значения этой переменной передается методу. Переменные ссылочного типа хранят ссылки на объекты, поэтому указание переменной ссылочного типа в качестве аргумента передает методу копию фактической ссылки, ссылающейся на объект. Даже если сама ссылка передается по значению, метод может использовать полученную ссылку для взаимодействия с исходным объектом и, возможно, для его изменения. Аналогично, при возврате информации из метода через оператор return метод возвращает копию значения, хранящегося в переменной типа значения, или копию ссылки, хранящейся в переменной типа ссылки. Когда ссылка возвращается, вызывающий метод может использовать эту ссылку для взаимодействия с указанным объектом. Таким образом, по сути, объекты всегда передаются по ссылке.

В c # для передачи переменной по ссылке, чтобы вызываемый метод мог изменять переменные, C # предоставляет ключевые слова ref и out. Применение ключевого слова ref к объявлению параметра позволяет передавать переменную в метод по ссылке - вызываемый метод сможет изменять исходную переменную в вызывающей стороне. Ключевое слово ref используется для переменных, которые уже были инициализированы в вызывающем методе. Обычно, когда вызов метода содержит неинициализированную переменную в качестве аргумента, компилятор генерирует ошибку. Предшествующий параметр с ключевым словом out создает выходной параметр. Это указывает компилятору, что аргумент будет передан в вызываемый метод по ссылке и что вызываемый метод назначит значение исходной переменной в вызывающей программе. Если метод не присваивает значение выходному параметру на каждом возможном пути выполнения, компилятор генерирует ошибку. Это также препятствует тому, чтобы компилятор генерировал сообщение об ошибке для неинициализированной переменной, которая передается в качестве аргумента методу. Метод может возвратить вызывающей стороне только одно значение с помощью оператора return, но может вернуть много значений, указав несколько выходных (ref и / или out) параметров.

см. Обсуждение c # и примеры здесь текст ссылки

...