В чем разница между
- параметр, переданный по ссылке
- параметр передается по значению?
Не могли бы вы привести несколько примеров, пожалуйста?
В чем разница между
Не могли бы вы привести несколько примеров, пожалуйста?
Ответы:
Во-первых, различие между передачей по стоимости и передачей по ссылке, как это определено в теории CS, теперь устарело, поскольку техника, первоначально определенная как «передача по ссылке», с тех пор перестала пользоваться популярностью и в настоящее время редко используется. 1
Более новые языки 2, как правило, используют другую (но схожую) пару методов для достижения тех же результатов (см. Ниже), что является основным источником путаницы.
Вторым источником путаницы является тот факт, что в «передаче по ссылке» «ссылка» имеет более узкое значение, чем общий термин «ссылка» (потому что фраза предшествует этому).
Теперь, подлинное определение:
Когда параметр передается по ссылке , вызывающая сторона и вызываемая сторона используют одну и ту же переменную для параметра. Если вызываемый объект изменяет переменную параметра, эффект виден для переменной вызывающего.
Когда параметр передается по значению , вызывающая сторона и вызываемая сторона имеют две независимые переменные с одинаковым значением. Если вызываемый объект изменяет переменную параметра, эффект не будет виден вызывающему.
Что следует отметить в этом определении:
«Переменная» здесь означает саму переменную вызывающего (локальную или глобальную) - то есть, если я передам локальную переменную по ссылке и назначу ее, я изменю саму переменную вызывающего, а не, например, на то, на что она указывает, если это указатель ,
Значение «ссылка» в «передать по ссылке» . Разница с общим термином «ссылка» заключается в том, что эта «ссылка» является временной и неявной. В сущности, вызываемый объект получает «переменную», которая как-то «совпадает» с исходной. То, как конкретно достигается этот эффект, не имеет значения (например, язык может также раскрывать некоторые детали реализации - адреса, указатели, разыменование - это все не имеет значения; если это чистый эффект, то это передача по ссылке).
Теперь в современных языках переменные имеют тенденцию быть «ссылочными типами» (другая концепция, изобретенная позже, чем «передача по ссылке» и вдохновленная ими), то есть фактические данные объекта хранятся где-то отдельно (обычно в куче), и только «ссылки» на него когда-либо хранятся в переменных и передаются как параметры. 3
Передача такой ссылки подпадает под передачу по значению, поскольку технически значением переменной является сама ссылка, а не упомянутый объект. Тем не менее, общий эффект на программу может быть таким же, как передача по значению или передача по ссылке:
Как вы можете видеть, эта пара техник почти такая же, как в определении, только с уровнем косвенности: просто замените «переменную» на «объект ссылки».
Для них нет согласованного имени, что приводит к искаженным объяснениям, таким как «вызов по значению, где значение является ссылкой». В 1975 году Барбара Лисков предложила термин « обмен вызовами по объектам » (или иногда просто «обмен вызовами по обмену»), хотя он так и не получил широкого распространения. Более того, ни одна из этих фраз не проводит параллели с оригинальной парой. Неудивительно, что старые термины в конечном итоге использовались в отсутствие чего-то лучшего, что приводило к путанице. 4
ПРИМЕЧАНИЕ : в течение длительного времени этот ответ говорил:
Скажем, я хочу поделиться с вами веб-страницей. Если я скажу вам URL, я передаю по ссылке. Вы можете использовать этот URL, чтобы увидеть ту же веб-страницу, которую я вижу. Если эта страница изменилась, мы оба увидим изменения. Если вы удаляете URL, все, что вы делаете, это уничтожаете вашу ссылку на эту страницу - вы не удаляете саму страницу.
Если я распечатаю страницу и дам вам распечатку, я передам по значению. Ваша страница является отключенной копией оригинала. Никаких последующих изменений вы не увидите, а внесенные вами изменения (например, пометки на распечатке) не будут отображаться на исходной странице. Если вы уничтожили распечатку, вы фактически уничтожили свою копию объекта - но исходная веб-страница осталась нетронутой.
Это в основном правильно, за исключением более узкого значения «ссылка» - оно является как временным, так и неявным (это не обязательно, но быть явным и / или постоянным - это дополнительные функции, а не часть семантики передачи по ссылке) , как объяснено выше). Более близкая аналогия - дать вам копию документа и пригласить вас поработать над оригиналом.
1 Если вы не программируете на Fortran или Visual Basic, это не стандартное поведение, и в большинстве современных языков использование истинного вызова по ссылке даже невозможно.
2 Изрядное количество старших тоже поддерживают это
3 В нескольких современных языках все типы являются ссылочными типами. Этот подход был впервые введен языком CLU в 1975 году и с тех пор был принят многими другими языками, включая Python и Ruby. И многие другие языки используют гибридный подход, где некоторые типы являются «типами значений», а другие - «ссылочными типами» - среди них C #, Java и JavaScript.
4 Нет ничего плохого в переработке подходящего старого термина как такового, но нужно как-то прояснить, какой смысл используется каждый раз. Не делать это именно то, что продолжает вызывать замешательство.
Это способ передачи аргументов в функции. Передача по ссылке означает, что параметр вызываемых функций будет таким же, как переданный аргумент вызывающего (не значение, а идентификатор - сама переменная). Передача по значению означает, что параметр вызываемых функций будет копией переданного аргумента вызывающей стороны. Значение будет таким же, но идентичность - переменная - отличается. Таким образом, изменения параметра, выполняемого вызываемой функцией, в одном случае изменяют передаваемый аргумент, а в другом случае просто изменяют значение параметра в вызываемой функции (которая является только копией). В быстрой спешке:
ref
используемое в вызывающей и вызываемой функции). Джон Скит также имеет хорошее объяснение этого здесь .коды
Так как мой язык - 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
Этот парень в значительной степени прибивает это:
Многие ответы здесь (и, в частности, ответ с наибольшим количеством голосов) фактически неверны, так как они неправильно понимают, что на самом деле означает «вызов по ссылке». Вот моя попытка исправить ситуацию.
Проще говоря:
В метафорических терминах:
Обратите внимание, что обе эти концепции полностью независимы и ортогональны от концепции ссылочных типов (которая в Java - это все типы, которые являются подтипами Object
, а в C # - все class
типы) или от концепции типов указателей, подобных C (которые семантически эквивалентны). на «ссылочные типы» Java, просто с другим синтаксисом).
Понятие ссылочного типа соответствует URL: оно само является частью информации и является ссылкой ( указатель , если хотите) на другую информацию. У вас может быть много копий URL в разных местах, и они не меняют сайт, на который они все ссылаются; если сайт обновляется, то каждая копия URL будет по-прежнему приводить к обновленной информации. И наоборот, изменение URL в любом месте не повлияет на любую другую письменную копию URL.
Обратите внимание , что C ++ есть понятие «ссылка» (например int&
) , что является не как Java и C # 's „ссылочных типов“, но это как „вызов по ссылке“. «Ссылочные типы» в 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
, также объявлен в строке-arg
является переменной , конкретно локальной переменной функции bar
, объявленной и инициализирован в строке 2arg
также является аргументом для конкретного вызова в foo
строке 3Здесь следует выделить два очень важных набора понятий. Первый - это значение в зависимости от переменной :
bar
выше функции после строки int arg = 1;
выражение arg
имеет значение 1
.final
или C # readonly
) или может быть неизменной (например, с использованием C ++ const
).Другая важная пара понятий, которые следует различать, это параметр против аргумента :
При вызове по значению формальные параметры функции - это переменные, которые заново создаются для вызова функции и инициализируются значениями их аргументов.
Это работает точно так же, как любые другие виды переменных инициализируются значениями. Например:
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-адреса, нас не волнует (и на самом деле, в таких языках, как Java и Python, «URL», или значение ссылочного типа, может не может быть изменен, может только указанная им вещь).
Барбара Лисков, когда она изобрела язык программирования CLU (который имел эту семантику), поняла, что существующие термины «вызов по значению» и «вызов по ссылке» не особенно полезны для описания семантики этого нового языка. Таким образом, она изобрела новый термин: вызов через разделение объектов .
При обсуждении языков, которые технически называются по значению, но в которых распространенными типами являются ссылочный тип или тип указателя (т. Е. Почти каждый современный императивный, объектно-ориентированный или мультипарадигмальный язык программирования), я нахожу, что его гораздо меньше смущает просто избегайте разговоров о звонке по значению или звонке по ссылке . Придерживайтесь вызова через общий доступ к объектам (или просто вызов по объекту ), и никто не будет смущен. :-)
The first is value versus variable.
The other important pair of concepts to distinguish is parameter versus argument:
Прежде чем понимать 2 условия, вы ДОЛЖНЫ понять следующее. У каждого объекта есть 2 вещи, которые могут его выделить.
Так что если вы скажете employee.name = "John"
знаю, что есть 2 вещи о name
. Его значение , которое , "John"
а также его расположение в памяти , которая некоторое шестнадцатеричное число , может быть , как это: 0x7fd5d258dd00
.
В зависимости от архитектуры языка или типа (класса, структуры и т. Д.) Вашего объекта, вы будете либо передавать, "John"
либо0x7fd5d258dd00
Передача "John"
называется передачей по значению. Передача 0x7fd5d258dd00
называется передачей по ссылке. Любой, кто указывает на эту область памяти, будет иметь доступ к значению "John"
.
Более подробно об этом я рекомендую вам прочитать о разыменовании указателя, а также о том, почему следует выбирать struct (тип значения) вместо класса (ссылочный тип)
Вот пример:
#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
}
y
уже было установлено на 2 предыдущей строкой. Почему бы вернуться к 0?
Самый простой способ получить это - файл Excel. Скажем, например, что у вас есть два числа, 5 и 2 в ячейках A1 и B1 соответственно, и вы хотите найти их сумму в третьей ячейке, скажем, A2. Вы можете сделать это двумя способами.
Либо, передав их значения в ячейку A2 , введя = 5 + 2 в эту ячейку. В этом случае, если значения ячеек A1 или B1 изменяются, сумма в A2 остается неизменной.
Или передавая «ссылки» ячеек A1 и B1 в ячейку A2 , набрав = A1 + B1 . В этом случае, если значения ячеек A1 или B1 изменяются, сумма в A2 также изменяется.
При передаче по ref вы в основном передаете указатель на переменную. Передавая по значению, вы передаете копию переменной. При базовом использовании это обычно означает, что переданные по ref изменения в переменной будут рассматриваться как вызывающий метод, а переданные по значению они не будут.
При передаче по значению отправляется КОПИЯ данных, хранящихся в указанной вами переменной, при передаче по ссылке отправляется прямая ссылка на саму переменную. Поэтому, если вы передадите переменную по ссылке, а затем измените переменную внутри блока, в который она была передана, исходная переменная будет изменена. Если вы просто передадите по значению, исходная переменная не сможет быть изменена блоком, в который вы ее передали, но вы получите копию того, что она содержала во время вызова.
Передача по значению - функция копирует переменную и работает с копией (поэтому она ничего не меняет в исходной переменной)
Передача по ссылке - функция использует исходную переменную; если вы изменяете переменную в другой функции, она также изменяется и в исходной переменной.
Пример (скопируйте и используйте / попробуйте сами и посмотрите):
#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;
}
Держите это просто, выглядывает. Стены текста могут быть вредной привычкой.
Основное различие между ними заключается в том, что переменные типа значения хранят значения, поэтому указание переменной типа значения в вызове метода передает копию значения этой переменной в метод. Переменные ссылочного типа хранят ссылки на объекты, поэтому указание переменной ссылочного типа в качестве аргумента передает методу копию фактической ссылки, которая ссылается на объект. Даже если сама ссылка передается по значению, метод может использовать полученную ссылку для взаимодействия с исходным объектом и, возможно, для его изменения. Аналогично, при возврате информации из метода через оператор return метод возвращает копию значения, хранящегося в переменной типа значения, или копию ссылки, хранящейся в переменной типа ссылки. Когда ссылка возвращается, вызывающий метод может использовать эту ссылку для взаимодействия с указанным объектом. Так,
В c # для передачи переменной по ссылке, чтобы вызываемый метод мог изменять переменные, C # предоставляет ключевые слова ref и out. Применение ключевого слова ref к объявлению параметра позволяет передавать переменную в метод по ссылке - вызываемый метод сможет изменять исходную переменную в вызывающей стороне. Ключевое слово ref используется для переменных, которые уже были инициализированы в вызывающем методе. Обычно, когда вызов метода содержит неинициализированную переменную в качестве аргумента, компилятор генерирует ошибку. Предшествующий параметр с ключевым словом out создает выходной параметр. Это указывает компилятору, что аргумент будет передан в вызываемый метод по ссылке и что вызываемый метод назначит значение исходной переменной в вызывающей программе. Если метод не присваивает значение выходному параметру на каждом возможном пути выполнения, компилятор генерирует ошибку. Это также препятствует тому, чтобы компилятор генерировал сообщение об ошибке для неинициализированной переменной, которая передается в качестве аргумента методу. Метод может возвратить вызывающей стороне только одно значение с помощью оператора return, но может вернуть много значений, указав несколько выходных (ref и / или out) параметров.
см. обсуждение c # и примеры здесь, текст ссылки
Примеры:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
const &
как правило, лучше. Вы не несете штраф за строительство и разрушение. Если ссылка не является константой, ваш интерфейс предполагает, что она изменит переданные данные.
Короче говоря, Passed by value - это то, ЧТО это и передается по ссылке, ГДЕ это.
Если ваше значение VAR1 = «Счастливый парень!», Вы увидите только «Счастливый парень!». Если VAR1 изменится на «Happy Gal!», Вы об этом не узнаете. Если он передается по ссылке, а VAR1 изменяется, вы это сделаете.
Если вы не хотите изменять значение исходной переменной после ее передачи в функцию, функция должна быть построена с параметром « pass by value ».
Тогда функция будет иметь ТОЛЬКО значение, но не адрес переданной переменной. Без адреса переменной код внутри функции не может изменить значение переменной, как видно снаружи функции.
Но если вы хотите дать функции возможность изменять значение переменной, как видно снаружи, вам нужно использовать передачу по ссылке . Поскольку значение и адрес (ссылка) передаются и доступны внутри функции.
передача по значению означает, как передать значение в функцию, используя аргументы. при передаче по значению мы копируем данные, хранящиеся в указанной переменной, и это происходит медленнее, чем передача по ссылке, поскольку данные копируются. Мы вносим изменения в скопированные данные, исходные данные не затрагиваются. При передаче по ссылке или по адресу мы отправляем прямую ссылку на саму переменную. или передав указатель на переменную. это быстрее, потому что меньше времени потребляется
Вот пример, который демонстрирует различия между передачей по значению - значением указателя - ссылкой :
void swap_by_value(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
void swap_by_pointer(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap_by_reference(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int arg1 = 1, arg2 = 2;
swap_by_value(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 1 2
swap_by_pointer(&arg1, &arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
arg1 = 1; //reset values
arg2 = 2;
swap_by_reference(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
}
Метод «передачи по ссылке» имеет важное ограничение . Если параметр объявлен как переданный по ссылке (поэтому ему предшествует знак &), его соответствующий фактический параметр должен быть переменной .
Фактический параметр, ссылающийся на формальный параметр «передано по значению», может быть выражением в целом, поэтому допускается использование не только переменной, но также литерала или даже результата вызова функции.
Функция не может поместить значение в нечто иное, чем переменная. Он не может присвоить литералу новое значение или заставить выражение изменить его результат.
PS: Вы также можете проверить ответ Дилана Битти в текущей ветке, которая объясняет его простыми словами.