和有什么区别
- 通过引用传递的参数
- 按值传递的参数?
请给我一些例子好吗?
首先,CS 理论中定义的“按值传递与按引用传递”的区别现在已经过时,因为最初定义为“按引用传递”的技术已经失宠并且现在很少使用。1
较新的语言2倾向于使用不同(但相似)的一对技术来实现相同的效果(见下文),这是造成混淆的主要原因。
混淆的第二个来源是,在“通过引用”中,“引用”的含义比一般术语“引用”更窄(因为该短语早于它)。
现在,真实的定义是:
当通过引用传递参数时,调用者和被调用者使用相同的变量作为参数。如果被调用者修改了参数变量,效果对调用者的变量是可见的。
当一个参数通过值传递时,调用者和被调用者有两个具有相同值的自变量。如果被调用者修改了参数变量,则调用者看不到效果。
在这个定义中需要注意的是:
这里的“变量”是指调用者的(局部或全局)变量本身——即,如果我通过引用传递一个局部变量并分配给它,我将更改调用者的变量本身,而不是它所指向的任何东西,如果它是一个指针.
“引用传递”中“引用”的含义。与一般的“参考”术语不同的是,这个“参考”是暂时的和隐含的。被调用者基本上得到的是一个“变量”,它在某种程度上与原始变量“相同”。实现这种效果的具体方式是无关紧要的(例如,语言还可能公开一些实现细节——地址、指针、取消引用——这都是无关紧要的;如果最终效果是这样,它就是通过引用传递)。
现在,在现代语言中,变量往往是“引用类型”(另一个概念是在“通过引用”之后发明并受到它的启发),即实际的对象数据单独存储在某个地方(通常在堆上),并且只有对它的“引用”才会保存在变量中并作为参数传递。3
传递这样的引用属于按值传递,因为变量的值在技术上是引用本身,而不是被引用的对象。但是,对程序的净影响可能与按值传递或按引用传递相同:
如您所见,这对技术与定义中的技术几乎相同,只是有一定程度的间接性:只需将“变量”替换为“引用对象”即可。
它们没有商定的名称,这导致了扭曲的解释,例如“在值是引用的情况下按值调用”。1975 年,Barbara Liskov 提出了“ call-by-object-sharing ”(或有时只是“call-by-sharing”)这一术语,尽管它从未完全流行起来。此外,这些短语都没有与原始对平行。难怪旧术语最终在没有更好的东西的情况下被重用,导致混乱。4
注意:很长一段时间,这个答案曾经说:
假设我想与你分享一个网页。如果我告诉你 URL,我是通过引用传递的。您可以使用该 URL 来查看我可以看到的同一网页。如果该页面发生更改,我们都会看到更改。如果您删除 URL,您所做的只是破坏您对该页面的引用 - 您并没有删除实际页面本身。
如果我打印出页面并给您打印输出,我就是按价值传递。您的页面是原始的断开连接的副本。您不会看到任何后续更改,并且您所做的任何更改(例如在打印输出上涂鸦)都不会显示在原始页面上。如果您销毁打印输出,您实际上已经销毁了您的对象副本 - 但原始网页保持不变。
这几乎是正确的,除了“引用”的更狭义的含义——它既是临时的又是隐含的(它不是必须的,但是显式和/或持久是附加功能,而不是传递引用语义的一部分,如上所述)。一个更接近的类比是给你一份文件的副本,而不是邀请你处理原件。
1除非您使用 Fortran 或 Visual Basic 进行编程,否则这不是默认行为,而且在现代使用的大多数语言中,甚至不可能实现真正的按引用调用。
2相当多的老年人也支持它
3在几种现代语言中,所有类型都是引用类型。这种方法是由语言 CLU 在 1975 年开创的,此后被许多其他语言采用,包括 Python 和 Ruby。还有更多的语言使用混合方法,其中一些类型是“值类型”,而另一些是“引用类型”——其中包括 C#、Java 和 JavaScript。
4重复使用一个合适的旧术语本身并没有什么不好,但必须以某种方式明确每次使用的含义。不这样做正是导致混乱的原因。
这是一种如何将参数传递给函数的方法。通过引用传递意味着被调用函数的参数将与调用者传递的参数相同(不是值,而是标识 - 变量本身)。按值传递意味着被调用函数的参数将是调用者传递参数的副本。该值将是相同的,但身份 - 变量 - 是不同的。因此,在一种情况下,被调用函数对参数所做的更改会更改传递的参数,而在另一种情况下,只会更改被调用函数中参数的值(这只是一个副本)。匆匆忙忙:
ref
在调用者和被调用函数处使用的关键字)。Jon Skeet在这里也有一个很好的解释。代码
由于我的语言是 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# 的“引用类型”,而是像“引用调用”。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
也是第 3 行特定调用的参数foo
这里有两组非常重要的概念需要区分。第一个是值与变量:
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 副本上乱涂乱画的事实不会影响我的 URL 副本,这不是我们关心的事情(事实上,在 Java 和 Python 等语言中,“URL”或引用类型值可以'根本不能修改,只有它指向的东西可以)。
Barbara Liskov 在发明 CLU 编程语言(具有这些语义)时,意识到现有术语“按值调用”和“按引用调用”对于描述这种新语言的语义并不是特别有用。于是她发明了一个新名词:对象共享调用。
在讨论技术上按值调用的语言时,但使用的常见类型是引用或指针类型(即:几乎所有现代命令式、面向对象或多范式编程语言),我发现它不那么令人困惑简单地避免谈论按值调用或按引用调用。坚持通过对象共享调用(或简单地通过对象调用),没有人会感到困惑。:-)
在理解这两个术语之前,您必须了解以下内容。每个对象都有 2 个可以区分它的东西。
所以如果你说employee.name = "John"
知道有两件事name
。它的值"John"
也是它在内存中的位置,它是一些十六进制数,可能像这样:0x7fd5d258dd00
.
根据语言的体系结构或对象的类型(类、结构等),您将转移"John"
或0x7fd5d258dd00
传递"John"
被称为按值传递。传递0x7fd5d258dd00
被称为通过引用传递。任何指向此内存位置的人都可以访问"John"
.
有关这方面的更多信息,我建议您阅读有关取消引用指针以及为什么选择结构(值类型)而不是类(引用类型)
这是一个例子:
#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
}
最简单的方法是使用 Excel 文件。例如,假设您在单元格 A1 和 B1 中有两个数字 5 和 2,并且您想在第三个单元格中找到它们的总和,例如 A2。您可以通过两种方式做到这一点。
通过在此单元格中键入= 5 + 2将它们的值传递给单元格 A2。在这种情况下,如果单元格 A1 或 B1 的值发生变化,则 A2 中的总和保持不变。
或者通过键入= A1 + B1将单元格 A1 和 B1 的“引用”传递给单元格 A2。在这种情况下,如果单元格 A1 或 B1 的值发生变化,则 A2 中的总和也会发生变化。
通过 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 &
一般是最好的。您不会受到建造和破坏的惩罚。如果引用不是 const,则您的界面暗示它将更改传入的数据。
如果您不想在将原始变量传递给函数后更改其值,则应使用“按值传递”参数构造函数。
然后该函数将只有值,但没有传入变量的地址。如果没有变量的地址,函数内部的代码就无法改变从函数外部看到的变量值。
但是如果你想让函数能够改变从外部看到的变量的值,你需要使用pass by reference。因为值和地址(引用)都被传入并且在函数内部可用。
简而言之,按值传递就是它是什么,按引用传递就是它在哪里。
如果你的值是 VAR1 = "Happy Guy!",你只会看到 "Happy Guy!"。如果 VAR1 更改为“Happy Gal!”,您将不会知道。如果它是通过引用传递的,并且 VAR1 发生了变化,你会的。
pass by value means how to pass value to a function by making use of arguments. in pass by value we copy the data stored in the variable we specify and it is slower than pass by reference bcse t he data is copied . of we make changes in the copied data the original data is not affected. nd in pass by refernce or pass by address we send direct link to the variable itself . or passing pointer to a variable. it is faster bcse less time is consumed
这是一个示例,演示了按值传递 - 指针值 - 引用 之间的区别:
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 的答案,该答案用简单的文字解释。
问题是“vs”。
并且没有人指出重要的一点。
在传递值时,会占用额外的内存来存储传递的变量值。
在
传递引用时,不会为这些值占用额外的内存。(在某些情况下内存有效)。
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
在引用调用中,只有一个区别。我们使用&
ie 地址操作符。通过这样做
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)
。因此,函数内部所做的任何更改都将反映在外部。