传递参考与传递值有什么区别?
有什么区别
- 通过引用传递的参数
- 通过值传递的参数?
请给我一些例子吗?
当通过引用传递参数时,调用者和被调用者使用相同的variables作为参数。 如果被调用者修改了参数variables,则调用者的variables可以看到该效果。
当一个参数按值传递时 ,调用者和被调用者有两个具有相同值的独立variables 。 如果被调用者修改参数variables,调用者不可见该效果。
但是 ,如果所讨论的值是一个可变的引用types对象,或者是间接引用了其他值,则可以在值调用环境中模拟引用引用:如果被调用者修改了对象(或其他指向的值到对象),这些修改对调用者是可见的。 但是仿真不完全相同,因为只能对对象进行修改,而不能对variables进行修改。 这导致扭曲的解释,如“价值是一个参考价值的呼唤”。 这种令人困惑的事情是当今有多lessstream行的编程语言,因此人们经常会将按值传递的可变对象与引用调用混淆起来。
有关更多解释,请参阅下面的其他答案。
注 :很长一段时间,这个答案用于说:
说我想和你分享一个网页。 如果我告诉你的url,我通过参考。 您可以使用该url查看我可以看到的相同网页。 如果该页面被更改,我们都会看到更改。 如果你删除了这个URL,你所做的只是摧毁你对这个页面的引用 – 而不是删除实际的页面本身。
如果我打印出这个页面并给你打印输出,那么我就是有价值的。 您的页面是原件的断开连接的副本。 您将不会看到任何后续更改,并且您所做的任何更改(例如在打印输出上涂鸦)都不会显示在原始页面上。 如果你摧毁了打印输出,你实际上已经销毁了你的对象副本 – 但原始网页保持不变。
这是一个简单的比喻,很容易理解,这得到了这个职位数百upvotes。 然而,这个比喻是有缺陷的:引用引用和值引用不像URL。 (像C#这样的语言中的引用types就像一个URL;请参阅Jon Skeet关于.NET中引用types的详细介绍,但是引用types与通过引用不同) 。
由于这个比喻实际上并不正确,所以已经从这个答案中删除了。 请参阅下面的讨论内容。
这是如何将parameter passing给函数的一种方式。 通过引用传递意味着被调用函数的参数将与调用者传递的参数(不是值,而是标识 – variables本身)相同。 按值传递意味着被调用函数的参数将是调用者传递参数的副本。 价值将是相同的,但身份 – variables – 是不同的。 因此,在一种情况下,由被调用函数完成的参数变化会改变传递的参数,而在另一种情况下,只会改变被调用函数中的参数值(仅为副本)。 急急:
- Java只支持按值传递。 始终复制参数,即使在复制对象的引用时,被调用函数中的参数也会指向同一个对象,并且将在调用者中看到对该对象的更改。 既然这可能会让人困惑,那么Jon Skeet就是这样说的。
- C#支持按值传递和按引用传递(在调用者和被调用函数中使用关键字
ref
)。 Jon Skeet 在这里也有很好的解释。 - C ++支持按值传递和按引用传递(在被调用函数中使用的引用参数types)。 你会发现下面的解释。
代码
由于我的语言是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
这家伙几乎钉钉子:
这里是一个例子:
#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 }
这里的许多答案(特别是最高的答案)事实上是不正确的,因为他们误解了“通过引用来呼叫”的真正含义。 这是我试图确定的事情。
TL; DR
最简单的说法是:
- 按值调用意味着您将值作为函数parameter passing
- 通过引用调用意味着您将variables作为函数parameter passing
用隐喻的话来说:
- 按价值打电话是我在一张纸上写下一些东西,然后交给你 。 也许这是一个url,也许这是一个战争与和平的完整副本。 不pipe它是什么,它都放在我给你的一张纸上,所以现在它就是你的一张纸了 。 现在你可以自由地在那张纸上涂鸦,或者用那张纸在别的地方寻找一些东西,随便摆弄它。
- 通过参考来电是我给你的笔记本里写下来的东西 。 你可以在我的笔记本上涂写(也许我想要你,也许我没有),然后我把笔记本放在那里,随便写什么。 另外,如果你或我写的是关于如何在其他地方find某些东西的信息,那么你或者我可以去那儿找那些信息。
什么“按价值打电话”和“按参考电话” 并不意味着什么
请注意,这两个概念与引用types (在Java中是所有types都是Object
子types,以及C#中的所有class
types)的概念是完全独立的并且是正交的,或者类似于C中的指针types的概念语义上相当于Java的“引用types”,只是语法不同)。
引用types的概念对应于一个URL:它本身就是一个信息,它是一个引用 (如果你愿意的话)指向其他信息。 你可以在不同的地方有很多URL的副本,而且不会改变他们链接的网站。 如果网站更新,那么每个url副本仍然会导致更新的信息。 相反,在任何一个地方更改URL都不会影响URL的任何其他书面副本。
请注意,C ++有一个不像Java和C#的“引用types”的“引用”(如int&
)的概念,但是就像“引用调用”一样。 Java和C#的“引用types”,以及Python中的所有types,就像C和C ++所称的“指针types”(如int*
)。
好,这是更长,更正式的解释。
术语
首先,我想强调一些重要的术语,帮助澄清我的答案,并确保我们在使用单词时都指向相同的想法。 (实际上,我认为绝大多数关于这些话题的混淆源于用不完全expression意图的意思的方式来使用词语。)
首先,下面是一个函数声明的类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
是一个variables ,特别是函数bar
的局部variables ,在第2行声明和初始化 -
arg
也是在第3行中具体调用foo
一个参数
这里有两个很重要的概念来区分。 首先是价值与variables :
- 值是评估语言中的expression式的结果 。 例如,在上面的
bar
函数中,行int arg = 1;
,expression式arg
的值为1
。 - variables是值的容器 。 一个variables可以是可变的(这是大多数类C语言中的默认types),只读(例如,使用Java的
final
或C#的readonly
)或深度不可变的(例如使用C ++的const
)。
另一个重要的概念来区分是参数与参数 :
- 参数 (也称为forms参数 )是调用者在调用函数时必须提供的variables 。
- 参数是由函数的调用者提供的值 ,以满足该函数的特定forms参数
按价值调用
在按值调用时 ,函数的forms参数是为函数调用而新创build的variables,它们用参数的值进行初始化。
这与任何其他types的variables都使用值进行初始化的方式完全相同。 例如:
int arg = 1; int another_variable = arg;
这里arg
和another_variable
是完全独立的variables – 它们的值可以相互独立地改变。 但是,在声明another_variable
的地方,它被初始化为保持arg
保持的相同值 – 即1
。
由于它们是独立variables,因此对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
)接收值作为参数,但是对于来自调用者的variables(在这种情况下为bar
)的值具有其自己的单独variables 。
回到我上面的隐喻,如果我是bar
,你是foo
,当我给你打电话时,我会递给你一张写有价值的纸。 你叫那张纸param
。 该值是我写在我的笔记本(我的本地variables),在我调用arg
的variables的值的副本 。
(顺便说一下,根据硬件和操作系统的不同,关于如何从另一个函数调用一个函数,有各种调用约定 ,调用约定就像我们决定是否将该值写在一张纸上然后交给你,或者如果你把一张纸写在上面,或者把它写在我们两个人面前的墙上,这也是一个有趣的话题,但远远超出了这个已经很长的答案的范围。
通过参考调用
在引用调用中 ,函数的forms参数只是与调用者提供的参数相同的variables的新名称 。
回到上面的例子,它相当于:
// 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
另一个名字 – 也就是说,它们是相同的variables ,所以param
变化反映在arg
。 这是通过引用进行呼叫与按值呼叫不同的基本方式。
很less有语言支持通过引用来调用,但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或几乎任何其他stream行语言的作用。 这意味着这些语言不是通过引用来调用,而是通过值来调用。
附录:通过对象共享呼叫
如果你有什么是按值调用 ,但实际值是一个引用types或指针types ,那么“值”本身并不是很有趣(例如在C中它只是一个平台特定的大小的整数) – 什么是有趣的是这个价值指向什么。
如果该引用types(即指针)指向的内容是可变的,则可能会产生一个有趣的效果:您可以修改指向的值,并且调用者可以观察指向值的更改,即使调用者无法观察改变指针本身。
为了再次借用URL的类比,如果我们关心的是网站,而不是url,那么我给了您一个网站的URL 副本的事实并不特别有趣。 事实上,你抄写的URL不影响我的URL副本不是我们关心的事情(事实上,在像Java和Python这样的语言中,“URL”或引用types值可以根本不能修改,只有它指出的东西可以)。
芭芭拉·利斯科夫(Barbara Liskov)在发明CLU编程语言(有这些语义)时,意识到现有的“按价值调用”和“按引用调用”这两个术语对描述这种新语言的语义并不是特别有用。 于是她发明了一个新术语: 通过对象共享呼叫 。
当讨论技术上按值来调用的语言,但是常用的types是引用或指针types(几乎每一种现代命令式,面向对象或多范式的编程语言)的时候,我觉得这种语言对于简单地避免谈论价值的 呼叫或通过参考呼叫 。 坚持通过对象共享 (或简单地通过对象 ) 调用 ,没有人会感到困惑。 🙂
比较:价值与参考
按值传递本地参数是传递给原始参数的副本在函数中对这些variables所做的 更改 不会影响原始内容
按引用传递本地参数是对传入的原始参数的存储位置的引用。对函数中这些variables的更改将影响原始文件不进行复制,因此保存了复制(时间,存储)开销
当通过ref传递你基本上是一个指针传递给variables。 按值传递variables的副本。 在基本的用法中,这通常意味着通过ref的变化,variables将被视为调用方法,并通过值他们不会。
最简单的方法是在Excel文件上。 比方说,例如,在单元格A1和B1中有两个数字5和2,并且您想要在第三个单元格中find他们的总和,比方说A2。 你可以用两种方法做到这一点。
-
要么通过在这个单元格中键入= 5 + 2 将它们的值传递给单元格A2 。 在这种情况下,如果单元格A1或B1的值发生变化,则A2中的和保持不变。
-
或通过键入= A1 + B1 将单元格A1和B1的“引用”传递给单元格A2 。 在这种情况下,如果单元格A1或B1的值发生变化,则A2中的和也会发生变化。
在理解这两个术语之前,您必须理解以下内容。 每个对象都有两件事情可以使它被区分开来。
- 它的价值。
- 它的地址。
所以如果你说Employee.name = John
知道name
有两件事。 它的值是John
,也是它在内存中的一些hex数字的位置可能是这样的: 0x7fd5d258dd00
。
根据语言的体系结构,您可能会传输John
或0x7fd5d258dd00
传递John
被认为是通过价值。 传递0x7fd5d258dd00
被视为传递引用。 任何指向这个内存位置的人都可以访问John
的值。
有关这方面的更多信息,我build议您阅读有关解除引用指针的信息,以及为什么selectstruct over class
通过值传递一个COPY的数据存储在你指定的variables中,通过引用传递一个直接链接到variables本身。 所以,如果你通过引用传递一个variables,然后改变你传入的块内的variables,原来的variables将被改变。 如果你只是按值传递,原来的variables将不能被你传递给它的块所改变,但你将得到在调用时所包含的任何东西的副本。
按值传递 – 函数复制variables并使用一个副本(所以它不会改变原始variables中的任何内容)
按引用传递 – 该函数使用原始variables,如果在另一个函数中更改该variables,则它也将在原始variables中变化。
示例(复制并使用/自己试试看):
#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; }
保持简单,偷看。 文字墙可能是一个坏习惯。
它们之间的一个主要区别是值typesvariables存储值,因此在方法调用中指定值typesvariables会将该variables值的副本传递给方法。 引用typesvariables存储对对象的引用,因此将引用typesvariables指定为参数将传递引用该对象的实际引用的副本。 即使引用本身是通过值传递的,该方法仍然可以使用它接收到的引用与原始对象进行交互(并可能修改)。 类似地,当通过return语句从方法返回信息时,该方法返回存储在值typesvariables中的值的副本或者存储在参考typesvariables中的引用的副本。 当返回引用时,调用方法可以使用该引用与引用的对象进行交互。 所以,实际上,对象总是被引用传递。
在c#中,要通过引用来传递variables,所以被调用的方法可以修改variables,C#提供关键字ref和out。 将ref关键字应用于参数声明允许您通过引用将variables传递给方法 – 被调用的方法将能够修改调用方中的原始variables。 ref关键字用于已经在调用方法中初始化的variables。 通常,当一个方法调用包含一个未初始化的variables作为参数时,编译器会产生一个错误。 在关键字out之前添加一个参数将创build一个输出参数。 这向编译器指出参数将通过引用被传递到被调用的方法中,被调用的方法将为调用者中的原始variables赋值。 如果该方法没有为每个可能的执行path中的输出参数赋值,则编译器会生成一个错误。 这也防止编译器为作为parameter passing给方法的未初始化variables生成错误消息。 一个方法只能通过return语句向其调用者返回一个值,但可以通过指定多个输出(ref和/或out)参数来返回许多值。
请参阅c#讨论和示例链接文本
总之,通过价值是什么,通过引用传递是在哪里。
如果你的价值是VAR1 =“幸福的家伙!”,你只会看到“幸福的家伙!”。 如果VAR1变成“Happy Gal!”,你就不会知道这一点。 如果它通过引用传递,并且VAR1改变,你会的。
根据定义,按值传递意味着您正在复制传入的实际参数值的副本,即实际参数内容的副本。 当你只是“使用”参数进行某些计算时,使用按值传递,而不是为客户端程序更改。
在通过引用(也称为通过地址)中,存储实际参数地址的副本。 当您更改由客户端程序传入的参数时,请使用引用传递。
按值传递意味着如何通过使用参数将值传递给函数。 在通过值的传递中,我们复制存储在我们指定的variables中的数据,并且比复制数据的速度慢。 我们对复制的数据进行更改,原始数据不受影响。 通过引用或通过地址传递我们直接链接到variables本身。 或者将指针传递给一个variables。 它消耗的时间更less
如果您不想在将原始variables传递给函数后更改其值,则应该使用“ 传值 ”参数构造该函数。
然后函数将只有值而不是传入的variables的地址。 没有variables的地址,函数内部的代码不能从函数的外部看到variables的值。
但是如果你想给函数改变从外部看到的variables值的能力,你需要使用引用传递 。 由于值和地址(参考)都被传入并在函数内部可用。
例子:
class Dog { public: barkAt( const std::string& pOtherDog ); // const reference barkAt( std::string pOtherDog ); // value };
const &
通常是最好的。 你不承担build设和销毁的惩罚。 如果引用不是const,那么你的接口会build议它改变传入的数据。
下面是一个例子,演示了按值传递之间的区别– 指针值 – 引用 :
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 }
“通过参考”的方法有一个重要的局限性 。 如果一个参数声明为引用传递 (所以前面加上&符号),其相应的实际参数必须是一个variables 。
引用“按值传递”forms参数的实际参数一般可以是一个expression式 ,所以不仅可以使用variables,而且可以使用文字甚至函数调用的结果。
该函数不能将一个值放在variables以外的值上。 它不能给一个文字分配一个新的值或强制一个expression式来改变其结果。
PS:你也可以在当前线程中检查Dylan Beattie的答案,用简单的语言来解释它。
按值:当参数通过值传递给方法时,这意味着实际variables的副本被发送到方法而不是实际方法,所以在方法内部应用的任何更改实际上都会影响副本的版本。
通过引用:当引用传递参数时,这意味着引用或指向实际variables的指针正在传递给方法,而不是实际的variables数据。
检查这篇文章进一步的例子。