通过引用传递和通过值传递之间的区别是什么?

两者有什么区别

  1. 引用传递的参数
  2. 参数通过value?

你能给我举几个例子吗?

839228 次浏览

这是一种将参数传递给函数的方法。通过引用传递意味着被调用函数的形参将与调用者传递的参数相同(不是值,而是恒等式——变量本身)。按值传递意味着被调用函数的形参将是调用方传递实参的副本。值将是相同的,但单位-变量-是不同的。因此,在一种情况下,被调用函数对形参的更改会改变传递的实参,而在另一种情况下,只会改变被调用函数中形参的值(这只是一个副本)。匆忙地:

  • Java只支持按值传递。总是复制实参,即使在复制对对象的引用时,被调用函数中的形参将指向相同的对象,并且对该对象的更改将在调用者中看到。因为这可能会令人困惑,在这里是Jon Skeet不得不说的。
  • c#支持按值传递和按引用传递(关键字ref用于调用者和被调用函数)。Jon Skeet也有一个很好的解释在这里
  • c++支持按值传递和按引用传递(在被调用函数中使用的引用参数类型)。您将在下面找到对此的解释。

代码

因为我的语言是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,你的接口暗示它将改变传入的数据。

按值传递发送存储在指定变量中的数据的复制,按引用传递发送到变量本身的直接链接。

因此,如果你通过引用传递一个变量,然后改变你传递给它的块中的变量,原始变量将被改变。如果只是按值传递,原始变量将不能被传递到的块所改变,但您将获得调用时它所包含的任何内容的副本。

它们之间的一个主要区别是值类型变量存储值,因此在方法调用中指定值类型变量将该变量值的副本传递给方法。引用类型变量存储对对象的引用,因此将引用类型变量指定为参数,将引用对象的实际引用的副本传递给方法。即使引用本身是按值传递的,方法仍然可以使用它接收到的引用与原始对象进行交互(可能还会修改)。类似地,当通过return语句从方法返回信息时,该方法返回存储在值类型变量中的值的副本或存储在引用类型变量中的引用的副本。当返回引用时,调用方法可以使用该引用与被引用的对象交互。因此,实际上,对象总是通过引用传递的。

在c#中,要通过引用传递一个变量,以便被调用的方法可以修改变量的值,c#提供了关键字ref和out。对参数声明应用ref关键字允许您通过引用将变量传递给方法——被调用的方法将能够在调用者中修改原始变量。ref关键字用于已经在调用方法中初始化的变量。通常,当方法调用包含一个未初始化的变量作为参数时,编译器会生成一个错误。在参数前加上关键字out将创建输出参数。这向编译器表明参数将通过引用传递给被调用的方法,并且被调用的方法将为调用方中的原始变量赋值。如果该方法没有在每个可能的执行路径中为输出参数赋值,编译器将生成一个错误。这还可以防止编译器为作为参数传递给方法的未初始化变量生成错误消息。一个方法只能通过return语句返回一个值给它的调用者,但是可以通过指定多个输出(ref和/或out)参数返回多个值。

参见c#讨论和示例链接文本

首先,“按价值传递”和“按参考传递”;CS理论中定义的区别现在已经过时了是因为该技术最初被定义为“引用传递”。已经失宠了吗和现在很少使用

较新的语言2倾向于使用不同(但相似)的技术来实现相同的效果(见下文),这是混淆的主要来源。

第二个混淆的来源是在“通过引用”中,“引用”;比一般术语“参考”的含义更窄;(因为这个短语比它更早)。


真实的定义是:

  • 当参数为通过引用时,调用方和被调用方为参数使用相同的变量。如果被调用方修改了参数变量,则对调用方的变量可见。

  • 当一个参数为通过值传递时,调用方和被调用方具有相同值的两个自变量。如果被调用方修改了参数变量,则对调用方不可见。

在这个定义中需要注意的是:

  • “Variable"这里指的是调用者的(局部或全局)变量本身—即,如果我通过引用传递一个局部变量并赋值给它,我将改变调用者的变量本身,而不是例如,无论它指向什么,如果它是指针。

    • 这现在被认为是不好的做法(作为隐式依赖)。因此,实际上,所有较新的语言都完全或几乎完全是值传递。引用传递现在主要以“输出/输入参数”的形式使用。在一个函数不能返回多个值的语言中。
  • < p > # EYZ0。与一般的“参考”的区别;这种效果是如何具体实现的是不相关的(例如,语言也可能暴露一些实现细节——地址,指针,解引用——这些都是不相关的;如果净效果是这样的,那么它就是引用传递)。


现在,在现代语言中,变量倾向于“引用类型”。(另一个发明于“参照传递”之后的概念;并受其启发),即实际的对象数据是单独存储在某个地方(通常是在堆上),并且只有"references"对于它的所有变量都保存在变量中并作为参数传递

传递这样的引用属于值传递,因为变量的值在技术上是引用本身,而不是被引用的对象。然而,# EYZ1

    如果引用只是从调用方的变量中获取并作为参数传递,这与引用传递具有相同的效果:如果被调用方中的引用对象是突变,则调用方将看到更改。
    • 但是,如果持有该引用的变量是重新分配,,它将停止指向该对象,因此对该变量的任何进一步操作都将影响它现在指向的对象。
    为了达到与值传递相同的效果,对象的副本在某个点被创建。选项包括:
    • 调用方可以在调用之前创建一个私有副本,并为被调用方提供对该副本的引用。
    • 在某些语言中,有些对象类型是“不可变的”:对它们进行的任何看似改变值的操作实际上都会创建一个全新的对象,而不会影响原始对象。因此,将这种类型的对象作为参数传递总是具有值传递的效果:当需要更改时,将自动为被调用方复制一个副本,而调用方的对象永远不会受到影响。
      • 在函数式语言中,所有对象是不可变的。

如您所见,这一对技巧与定义中的技巧几乎相同,只是在一定程度上间接了:只需将“variable”替换为“;使用“引用对象”。

它们没有统一的名称,这导致了一些扭曲的解释,比如“按值调用,而值是引用”。1975年,芭芭拉·利斯科夫提出了“call-by-object-sharing"(有时只是“分享呼叫”),尽管它从未流行起来。此外,这两个短语都不能与原来的短语相提并论。难怪老术语最终在没有更好的东西的情况下被重复使用,导致混乱

(我将使用术语“new"“indirect"按值传递和引用传递来表示新技术。)


请注意:很长一段时间以来,这个答案都是这样说的:

说我想和你分享一个网页。如果我告诉你网址,我 通过引用传递。您可以使用该URL查看相同的网页I 可以看到。如果页面被更改,我们都能看到更改。如果你 删除URL,你所做的就是破坏你对它的引用 页面-你不会删除实际的页面本身 如果我把这一页打印出来并给你打印出来的,我就是路过 价值。您的页面是原始页面的一个断开连接的副本。你不会看到 任何后续的更改,以及您所做的任何更改(例如,涂鸦 在你的打印输出)将不会显示在原页上。如果你 销毁打印输出,你实际上已经销毁了你的副本 对象-但原始的web页面保持完整

这是主要是正确的除了狭义意义的"参考"—它既是临时的,又是隐式的(它不需要,但是显式和/或持久是额外的特性,不是引用传递语义的一部分,如上所述)。一个更接近的类比是给你一份文件的副本,而不是邀请你处理原件。


# EYZ0 # EYZ1

# EYZ0 # EYZ1

# EYZ0 # EYZ1

# EYZ0 # EYZ1

如果你不想在将原始变量传递给函数后改变它的值,那么函数应该使用"按值传递"参数。

然后,函数将使用只有作为值,而不是传入变量的地址。如果没有变量的地址,函数内部的代码就不能改变从函数外部看到的变量值。

但是如果你想给这个函数改变变量值的能力,从外面看,你需要使用通过引用传递。因为值和地址(引用)都是传递进来的,并且在函数内部可用。

按值传递是指如何通过使用参数将值传递给函数。在按值传递中,我们复制存储在指定变量中的数据,并且它比按引用传递慢,因为数据是复制的。

或者我们对复制的数据进行更改。不影响原有数据。在按引用传递或按地址传递中,我们发送一个直接链接到变量本身。或者传递一个指向变量的指针。它更快是因为消耗的时间更少。

简而言之,通过值传递的是它是什么,通过引用传递的是它在哪里。

如果你的值是VAR1 = "Happy Guy!",你只会看到"Happy Guy!"如果VAR1变成了“Happy Gal!”,你就不会知道了。如果它是通过引用传递的,并且VAR1发生了变化,那么您就会这样做。

最简单的方法是使用Excel文件。举个例子,有两个数字,5和2分别在A1和B1单元格中,你想在第三个单元格中求出它们的和,假设是A2。

有两种方法可以做到这一点。

  • 在这个单元格中输入= 5 + 2。在这种情况下,如果单元格A1或B1的值发生变化,则A2中的总和保持不变。

  • 或输入= a1 + b1。在这种情况下,如果单元格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;

简单来说:

  • 按值调用表示将作为函数参数传递
  • 引用调用表示将变量作为函数参数传递

打个比方:

  • 按值调用我在一张纸上写下一些东西,然后递给你的位置。也许是一个URL,也许是《战争与和平》的完整拷贝。不管它是什么,它都在我给你们的一张纸上,所以现在它实际上是你的那张纸。你现在可以自由地在那张纸上涂鸦,或者用那张纸在其他地方找到一些东西,摆弄它,无论如何。
  • 引用调用是当我把我的笔记本给你,上面写着一些东西。你可以在我的笔记本上乱涂乱画(也许我想让你这样做,也许我不想),然后我把笔记本保存起来,上面写着你在上面写的任何东西。另外,如果你或我写的东西里有如何在其他地方找到某样东西的信息,你或我都可以去那里摆弄这些信息。

什么是“价值召唤”;而“推荐电话”;# EYZ0意味着

请注意,这两个概念都与< >强引用类型< / >强的概念(在Java中是Object的所有子类型,在c#中是所有class类型)或C中的< >强指针类型< / >强的概念(在语义上等价于Java的“引用类型”,只是语法不同)完全独立和正交。

引用类型的概念对应于一个URL:它本身是一个信息,它是一个参考(如果您愿意,也可以是指针)到其他信息。你可以在不同的地方有多个URL副本,它们不会改变它们链接到的网站;如果网站更新了,那么每个URL副本仍然会导致更新的信息。相反,在任何一个地方更改URL都不会影响URL的任何其他书面副本。

注意c++有一个“引用”的概念;(例如int&),即类似于Java和c#的“引用类型”,而类似于“引用调用”。Java和c#的“引用类型”,以及Python中的所有类型,就像C和c++中所谓的“指针类型”。(例如# EYZ1)。


好的,这里有一个更长更正式的解释。

术语

首先,我想强调一些重要的术语,以帮助澄清我的答案,并确保我们在使用词语时都指的是相同的想法。(在实践中,我相信绝大多数关于这类话题的困惑都源于使用词语的方式没有完全表达出想要表达的意思。)

首先,这里有一个类似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是一个变量,特别是函数bar局部变量,在第2行声明并初始化
  • arg也是第3行上foo论点到特定的调用

这里有两组非常重要的概念需要区分。第一个是价值变量:

  • < >强值< / >强是语言中的表达式求值的结果。例如,在上面的bar函数中,在行int arg = 1;之后,表达式arg价值 1
  • < >强变量< / >强就是值容器。变量可以是可变的(这是大多数类C语言的默认值)、只读的(例如使用Java的final或c#的readonly声明)或深度不可变的(例如使用c++的const)。

另一对需要区分的重要概念是参数论点:

  • 参数<强> < / >强(也称为形式参数)是一个变量,在调用函数时必须由调用者提供。
  • < >强参数< / >强是由函数的调用者提供的价值,以满足该函数的特定形式参数

按值调用

按值调用中,函数的形参是为函数调用新创建的变量,并使用它们的参数进行初始化。

这与任何其他类型的变量用值初始化的方式完全相同。例如:

int arg = 1;
int another_variable = arg;

这里arganother_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

这与上面例子中argparam之间的关系完全相同,为了对称,我将在这里重复一遍:

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或几乎任何其他流行语言的工作方式。这意味着这些语言是引用调用,它们是值调用。

附录:通过对象共享调用

如果你拥有的是按值调用,但实际值是引用类型指针类型,那么"value"它本身并不是很有趣(例如,在C中它只是一个特定平台大小的整数)——有趣的是指出的值。

如果该引用类型(即指针)所指向的是可变的,那么可能会出现一个有趣的效果:您可以修改指向值,而调用方可以观察到指向值的变化,即使调用方无法观察到指针本身的变化。

再次借用URL的类比,如果我们都关心的是网站而不是URL,那么我给你一个网站URL的复制就不是特别有趣了。事实上,你在你的URL副本上涂鸦并不影响我的URL副本,这不是我们关心的事情(事实上,在Java和Python等语言中,“URL”或引用类型值根本不能修改,只有它指向的东西可以修改)。

芭芭拉·利斯科夫(Barbara Liskov)在发明CLU编程语言(具有这些语义)时,意识到现有的术语“按值调用”;而“推荐电话”;对于描述这种新语言的语义并不是特别有用。所以她发明了一个新术语:通过对象共享调用

当讨论技术上按值调用的语言,但其中常用的类型是引用或指针类型(即:几乎所有现代命令式、面向对象或多范式编程语言)时,我发现简单地避免谈论按值调用引用调用会少很多混乱。坚持使用通过对象共享调用(或者简单地使用按对象调用),没有人会感到困惑。: -)

在理解这两个术语之前,您需要了解以下内容。每一个物体都有两个可以使它被区分的东西。

  • 它的价值。
  • 它的地址。

所以如果你说employee.name = "John",要知道关于name有两件事。它的值是"John",它在内存中的位置是一个十六进制数,可能像这样:0x7fd5d258dd00

根据语言的体系结构或对象的类型(类、结构等),您将传输"John"0x7fd5d258dd00

传递"John"被称为按值传递。

传递0x7fd5d258dd00被称为引用传递。任何指向这个内存位置的人都可以访问"John"的值。

关于这方面的更多信息,我建议你阅读对指针取消引用为什么选择结构(值类型)而不是类(引用类型)

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)。因此,在函数内部所做的任何更改都会在函数外部反映出来。

问题是&;vs"

没有人指出一个重要的点。在传递值时,会占用额外的内存来存储传递的变量值。

在传递引用时,值不会占用额外的内存(在某些情况下内存是有效的)。

看看这张照片:

在第一种情况(通过引用传递)中,当变量在函数内部设置或更改时,外部变量也会更改。

但在第二种情况(按值传递)中,改变函数内部的变量不会对外部变量产生任何影响。

要阅读本文,请参阅链接

引用调用vs值调用 .