Многие ответы здесь (и, в частности, наиболее высоко оцененный ответ) на самом деле неверны, поскольку они неправильно понимают, что на самом деле означает «вызов по ссылке». Вот моя попытка исправить положение.
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 (который имел эту семантику), поняла, что существующие термины «вызов по значению» и «вызов по ссылке» не особенно полезны для описания семантики этого нового языка. Поэтому она изобрела новый термин: вызов по совместному использованию объекта .
При обсуждении языков, которые технически называются по значению, но где распространенными типами являются ссылочные или указательные типы (то есть: почти каждый современный императивный, объектно-ориентированный или мультипарадигмальный язык программирования), я считаю, что это очень меньше путаницы, чтобы просто не говорить о вызове по значению или вызове по ссылке . Придерживайтесь вызова по объекту общего доступа (или просто вызова по объекту ), и никто не будет смущен. : -)