两者有什么区别
引用传递的参数 参数通过value?
你能给我举几个例子吗?
两者有什么区别
引用传递的参数 参数通过value?
你能给我举几个例子吗?
It's a way how to pass arguments to functions. Passing by reference means the called functions' parameter will be the same as the callers' passed argument (not the value, but the identity - the variable itself). Pass by value means the called functions' parameter will be a copy of the callers' passed argument. The value will be the same, but the identity - the variable - is different. Thus changes to a parameter done by the called function in one case changes the argument passed and in the other case just changes the value of the parameter in the called function (which is only a copy). In a quick hurry:
Java only supports pass by value. Always copies arguments, even though when copying a reference to an object, the parameter in the called function will point to the same object and changes to that object will be see in the caller. Since this can be confusing, here is what Jon Skeet has to say about this. C# supports pass by value and pass by reference (keyword ref used at caller and called function). Jon Skeet also has a nice explanation of this here. C++ supports pass by value and pass by reference (reference parameter type used at called function). You will find an explanation of this below.
代码
因为我的语言是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
当通过引用传递时,基本上是传递一个指向变量的指针。通过值传递,即传递变量的副本。
在基本用法中,这通常意味着通过引用传递,对变量的更改将在调用方法中看到,而在通过值传递时则不会。
这里有一个例子:
#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
}
例子:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
Const &通常是最好的。你不会受到建造和破坏的惩罚。如果引用不是const,你的接口暗示它将改变传入的数据。
“按值传递”将发送存储在指定变量中的数据的副本,“按引用传递”将发送到变量本身的直接链接。
因此,如果你通过引用传递一个变量,然后改变你传递给它的块中的变量,原始变量将被改变。如果只是按值传递,原始变量将不能被传递到的块所改变,但您将获得调用时它所包含的任何内容的副本。
A major difference between them is that value-type variables store values, so specifying a value-type variable in a method call passes a copy of that variable's value to the method. Reference-type variables store references to objects, so specifying a reference-type variable as an argument passes the method a copy of the actual reference that refers to the object. Even though the reference itself is passed by value, the method can still use the reference it receives to interact with—and possibly modify—the original object. Similarly, when returning information from a method via a return statement, the method returns a copy of the value stored in a value-type variable or a copy of the reference stored in a reference-type variable. When a reference is returned, the calling method can use that reference to interact with the referenced object. So, in effect, objects are always passed by reference.
In c#, to pass a variable by reference so the called method can modify the variable's, C# provides keywords ref and out. Applying the ref keyword to a parameter declaration allows you to pass a variable to a method by reference—the called method will be able to modify the original variable in the caller. The ref keyword is used for variables that already have been initialized in the calling method. Normally, when a method call contains an uninitialized variable as an argument, the compiler generates an error. Preceding a parameter with keyword out creates an output parameter. This indicates to the compiler that the argument will be passed into the called method by reference and that the called method will assign a value to the original variable in the caller. If the method does not assign a value to the output parameter in every possible path of execution, the compiler generates an error. This also prevents the compiler from generating an error message for an uninitialized variable that is passed as an argument to a method. A method can return only one value to its caller via a return statement, but can return many values by specifying multiple output (ref and/or out) parameters.
请参阅c#讨论和示例链接文本
首先,CS理论中定义的“通过值传递与通过引用传递”的区别现在已经过时了,因为最初定义为“通过引用传递”的技术已经不再受欢迎,现在很少使用
较新的语言2倾向于使用不同(但相似)的技术来实现相同的效果(见下文),这是混淆的主要来源。
第二个混淆的来源是,在“通过引用传递”中,“引用”的含义比一般术语“引用”更窄(因为这个短语比它更早)。
真实的定义是:
通过引用传递参数时,调用方和被调用方对参数使用相同的变量。如果被调用方修改了参数变量,则对调用方的变量可见。 当参数按值传递时,调用方和被调用方有两个具有相同值的自变量。如果被调用方修改了参数变量,则对调用方不可见。
在这个定义中需要注意的是:
"Variable" here means the caller's (local or global) variable itself -- i.e. if I pass a local variable by reference and assign to it, I'll change the caller's variable itself, not e.g. whatever it is pointing to if it's a pointer. This is now considered bad practice (as an implicit dependency). As such, virtually all newer languages are exclusively, or almost exclusively pass-by-value. Pass-by-reference is now chiefly used in the form of "output/inout arguments" in languages where a function cannot return more than one value. The meaning of "reference" in "pass by reference". The difference with the general "reference" term is that this "reference" is temporary and implicit. What the callee basically gets is a "variable" that is somehow "the same" as the original one. How specifically this effect is achieved is irrelevant (e.g. the language may also expose some implementation details -- addresses, pointers, dereferencing -- this is all irrelevant; if the net effect is this, it's pass-by-reference).
现在,在现代语言中,变量倾向于“引用类型”(另一个比“按引用传递”更晚发明的概念,并受到它的启发),即实际的对象数据被单独存储在某个地方(通常是在堆上),只有对它的“引用”被保存在变量中并作为参数传递
传递这样的引用属于值传递,因为从技术上讲,变量的值是引用本身,而不是被引用的对象。然而,对程序的最终影响可以是值传递或引用传递:
If a reference is just taken from a caller's variable and passed as an argument, this has the same effect as pass-by-reference: if the referred object is mutated in the callee, the caller will see the change. However, if a variable holding this reference is reassigned, it will stop pointing to that object, so any further operations on this variable will instead affect whatever it is pointing to now. To have the same effect as pass-by-value, a copy of the object is made at some point. Options include: The caller can just make a private copy before the call and give the callee a reference to that instead. In some languages, some object types are "immutable": any operation on them that seems to alter the value actually creates a completely new object without affecting the original one. So, passing an object of such a type as an argument always has the effect of pass-by-value: a copy for the callee will be made automatically if and when it needs a change, and the caller's object will never be affected. In functional languages, all objects are immutable.
正如你可能看到的,这对技术几乎与定义中的技术相同,只是在某种程度上间接地:只需将“变量”替换为“引用对象”。
它们没有统一的名称,这导致了一些扭曲的解释,比如“按值调用,其中值是引用”。1975年,Barbara Liskov提出了“按对象调用共享”(有时简称为“按对象调用共享”)这个术语,尽管它从未流行起来。此外,这两个短语都不能与原来的短语相提并论。难怪在没有更好的东西的情况下,旧的术语最终被重新使用,导致混乱
(对于新技术,我会使用术语“新的”或“间接的”值传递/引用传递。)
注:很长一段时间以来,这个答案都是这样说的:
Say I want to share a web page with you. If I tell you the URL, I'm passing by reference. You can use that URL to see the same web page I can see. If that page is changed, we both see the changes. If you delete the URL, all you're doing is destroying your reference to that page - you're not deleting the actual page itself. If I print out the page and give you the printout, I'm passing by value. Your page is a disconnected copy of the original. You won't see any subsequent changes, and any changes that you make (e.g. scribbling on your printout) will not show up on the original page. If you destroy the printout, you have actually destroyed your copy of the object - but the original web page remains intact.
这在很大程度上是正确的,除了狭义意义上的“引用”——它既是临时的又是隐式的(它不一定必须是临时的,但显式和/或持久性是额外的特性,不是引用传递语义的一部分,如上所述)。一个更接近的类比是给你一份文件的副本,而不是邀请你处理原件。
除非你用Fortran或Visual Basic编程,否则它不是默认行为,在现代使用的大多数语言中,真正的引用调用甚至是不可能的。
相当多的老年人也支持这一点
在一些现代语言中,所有类型都是引用类型。这种方法是由CLU语言在1975年首创的,后来被许多其他语言采用,包括Python和Ruby。还有更多的语言使用混合方法,其中一些类型是“值类型”,另一些是“引用类型”——其中包括c#、Java和JavaScript。
重复使用一个合适的旧术语本身并没有什么不好,但人们必须弄清楚每次使用的是什么意思。不这么做正是造成混乱的原因。
如果你不想在将原始变量传递给函数后改变它的值,那么函数应该构造一个“按值传递”参数。
然后函数将只有值,而没有传入变量的地址。如果没有变量的地址,函数内部的代码就不能改变从函数外部看到的变量值。
但是如果你想要赋予函数从外部看到的改变变量值的能力,你需要使用引用传递。因为值和地址(引用)都是传递进来的,并且在函数内部可用。
按值传递是指如何通过使用参数将值传递给函数。在按值传递中,我们复制存储在指定变量中的数据,并且它比按引用传递慢,因为数据是复制的。
或者我们对复制的数据进行更改。不影响原有数据。在按引用传递或按地址传递中,我们发送一个直接链接到变量本身。或者传递一个指向变量的指针。它更快是因为消耗的时间更少。
简而言之,通过值传递的是它是什么,通过引用传递的是它在哪里。
如果你的值是VAR1 = "Happy Guy!",你只会看到"Happy Guy!"如果VAR1变成了“Happy Gal!”,你就不会知道了。如果它是通过引用传递的,并且VAR1发生了变化,那么您就会这样做。
最简单的方法是在Excel文件中获取。举个例子,有两个数字,5和2分别在A1和B1单元格中,你想在第三个单元格中求出它们的和,假设是A2。
有两种方法可以做到这一点。
通过在单元格中输入= 5 + 2将它们的值传递给单元格A2。在这种情况下,如果单元格A1或B1的值发生变化,则A2中的总和保持不变。 或者通过输入= A1 + B1将单元格A1和B1的“引用”传递给单元格A2。在这种情况下,如果单元格A1或B1的值发生变化,A2中的总和也会发生变化。
下面是一个例子,演示了通过值-指针值-引用传递之间的区别:
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:你也可以在当前的线程中检查Dylan Beattie的答案,它用简单的语言解释了它。
通过值传递-函数复制变量并使用副本(因此它不会改变原始变量中的任何内容)
引用传递——函数使用原始变量。如果你改变了另一个函数中的变量,它也会改变原来的变量。
示例(复制并使用/自己尝试一下):
#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;
}
保持简单,伙计们。成堆的文字是个坏习惯。
这里的许多答案(特别是被点赞最多的答案)实际上是不正确的,因为他们误解了“参考呼叫”的真正含义。我来解释一下。
博士TL;
简单来说:
按值调用意味着将值作为函数参数传递 引用调用意味着将变量作为函数参数传递
打个比方:
Call by value is where I write down something on a piece of paper and hand it to you. Maybe it's a URL, maybe it's a complete copy of War and Peace. No matter what it is, it's on a piece of paper which I've given to you, and so now it is effectively your piece of paper. You are now free to scribble on that piece of paper, or use that piece of paper to find something somewhere else and fiddle with it, whatever. Call by reference is when I give you my notebook which has something written down in it. You may scribble in my notebook (maybe I want you to, maybe I don't), and afterwards I keep my notebook, with whatever scribbles you've put there. Also, if what either you or I wrote there is information about how to find something somewhere else, either you or I can go there and fiddle with that information.
“按值调用”和“按引用调用”不是什么意思
请注意,这两个概念都完全独立于引用类型的概念(在Java中引用类型是Object的所有子类型,在c#中是所有类类型),或者像C中那样的指针类型的概念(它们在语义上等价于Java的“引用类型”,只是语法不同)。
引用类型的概念对应于URL:它本身是一段信息,也是指向其他信息的引用(如果您愿意,也可以称为指针)。你可以在不同的地方有多个URL副本,它们不会改变它们链接到的网站;如果网站更新了,那么每个URL副本仍然会导致更新的信息。相反,在任何一个地方更改URL都不会影响URL的任何其他书面副本。
注意,c++有一个“引用”的概念(例如int&),它不像Java和c#的“引用类型”,而像“引用调用”。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是在第一行声明的函数(Java坚持使所有函数都是方法,但概念是一样的,但不失通用性;C和c++在声明和定义之间做了区分,我不会在这里深入讨论) Param是foo的正式形参,也在第1行声明 Arg是一个变量,具体来说是函数栏的一个局部变量,在第2行声明并初始化 Arg也是第3行特定foo调用的参数
这里有两组非常重要的概念需要区分。第一个是值对变量:
值是对语言中的表达式求值的结果。例如,在上面的bar函数中,在行int arg = 1;之后,表达式arg的值为1。 变量是值的容器。变量可以是可变的(这是大多数类C语言的默认值),只读的(例如使用Java的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,当我打电话给你时,我会递给你一张写着值的纸。你把这张纸叫做参数。这个值是我在我的笔记本上写的值(我的局部变量)的副本,在一个我称之为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”或引用类型值根本不能修改,只有它指向的东西可以修改)。
Barbara Liskov在发明CLU编程语言(具有这些语义)时,意识到现有的术语“按值调用”和“按引用调用”对于描述这种新语言的语义并不是特别有用。所以她发明了一个新术语:对象共享调用。
当讨论技术上按值调用的语言,但其中常用的类型是引用或指针类型(即:几乎所有现代命令式、面向对象或多范式编程语言)时,我发现简单地避免谈论按值调用或按引用调用会少得多混乱。坚持按对象共享调用(或简单地按对象调用),没有人会混淆。: -)
在理解这两个术语之前,您必须了解以下内容。每一个物体都有两个可以使它被区分的东西。
它的价值。 它的地址。
如果你说employee。name = "John",要知道关于name有两件事。它的值是“John”,它在内存中的位置是一个十六进制数,可能像这样:0x7fd5d258dd00。
根据语言的体系结构或对象的类型(类、结构等),可以传输“John”或0x7fd5d258dd00
传递“John”称为按值传递。
传递0x7fd5d258dd00称为引用传递。任何指向这个内存位置的人都可以访问“John”的值。
有关这方面的更多信息,我建议您阅读有关取消指针引用的内容,以及为什么选择struct(值类型)而不是类(引用类型)。
1. 按值传递/按值调用
void printvalue(int x)
{
x = x + 1 ;
cout << x ; // 6
}
int x = 5;
printvalue(x);
cout << x; // 5
在按值调用中,当你将一个值传递给printvalue(x)时,即参数5,它被复制为void printvalue(int x)。现在,我们有两个不同的值5和复制的值5,这两个值存储在不同的内存位置。因此,如果你在void printvalue(int x)内做了任何更改,它不会反射回实参。
2. 引用传递/引用调用
void printvalue(int &x)
{
x = x + 1 ;
cout << x ; // 6
}
int x = 5;
printvalue(x);
cout << x; // 6
在引用调用中,只有一个区别。我们使用&,即地址操作符。通过做 Void printvalue(int &x)我们引用的是x的地址,这告诉我们它指向相同的位置。因此,在函数内部所做的任何更改都会在函数外部反映出来。
既然你来了,你也应该知道……
3.按指针传递/按地址调用
void printvalue(int* x)
{
*x = *x + 1 ;
cout << *x ; // 6
}
int x = 5;
printvalue(&x);
cout << x; // 6
在传递地址中,指针int* x保存传递给它的地址printvalue(&x)。因此,在函数内部所做的任何更改都会在函数外部反映出来。
看看这张照片:
在第一种情况下(通过引用传递),当变量在函数内部设置或更改时,外部变量也会更改。
但在第二种情况下(按值传递),改变函数内部的变量不会对外部变量产生任何影响。
要阅读这篇文章,请参见这个链接。