按引用传递与按值传递有什么区别?

What's the difference between passing by reference vs. passing by value?

提问人: 提问时间:12/17/2008 最后编辑:Karl Knechtel 更新时间:7/13/2023 访问量:880286

问:

参数是“通过引用”或“按值”传递的是什么意思?这些参数有何不同?

与语言无关的 按引用传递

评论

0赞 11/11/2010
相关:如何在 C++ 中将对象传递给函数?
1赞 mfaani 3/4/2017
如果您不知道地址是什么,请参阅此处

答:

181赞 11 revs, 3 users 95%Johannes Schaub - litb #1

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

  • Java 仅支持按值传递。始终复制参数,即使在复制对对象的引用时,被调用函数中的参数将指向同一对象,并且调用方中将看到对该对象的更改。由于这可能会令人困惑,以下是 Jon Skeet 对此的看法。
  • C# 支持按值传递和按引用传递(在调用方和被调用函数处使用的关键字)。乔恩·斯基特(Jon Skeet)在这里也对此进行了很好的解释。ref
  • 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

评论

0赞 Avan 2/20/2022
将指针传递到函数的位置。Isn't 指针只是允许您修改它所指向的值,并且它会反映在指针指向的值上。形式参数(如果使用指针修改)。还应该改变参数吗?还是我错过了什么?这些不应该是引用的传递..然后?
23赞 Craig 12/17/2008 #2

通过引用传递时,您基本上是在传递指向变量的指针。按值传递,您正在传递变量的副本。

在基本用法中,这通常意味着通过引用传递,对变量的更改将在调用方法中看到,而在按值传递时则不会。

55赞 isekaijin 12/17/2008 #3

下面是一个示例:

#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
}

评论

1赞 Taimoor Changaiz 3/12/2014
我认为有一个问题,因为最后一行应该打印 0 而不是 2。如果我遗漏了什么,请告诉我。
0赞 isekaijin 3/12/2014
@TaimoorChangaiz;哪条“最后一行”?顺便说一句,如果你能使用IRC,请到Freenode上的##programming。在那里解释事情会容易得多。我的昵称是“pyon”。
1赞 Taimoor Changaiz 3/12/2014
@EduardoLeón by_val(y);std::cout << y << std::endl;印刷品 2
6赞 isekaijin 3/12/2014
@TaimoorChangaiz:为什么不打印 2? 已被上一行设置为 2。为什么它会回到 0?y
0赞 Taimoor Changaiz 3/13/2014
@EduardoLeón我的坏。是的,你是对的。感谢您的指正
3赞 DEADFACE 12/17/2008 #4

例子:

class Dog 
{ 
public:
    barkAt( const std::string& pOtherDog ); // const reference
    barkAt( std::string pOtherDog ); // value
};

const &通常是最好的。您不会受到建筑和破坏的处罚。如果引用不是常量,则接口建议它将更改传入的数据。

18赞 MetaGuru 12/17/2008 #5

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

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

5赞 Tina Endresen 12/17/2008 #6

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

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

请参阅此处的 C# 讨论和示例链接文本

1256赞 14 revs, 5 users 51%ivan_pozdeev #7

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

较新的语言2 倾向于使用一对不同(但相似)的技术来达到相同的效果(见下文),这是造成混淆的主要原因。

造成混淆的第二个原因是,在“通过引用”中,“引用”的含义比一般术语“引用”的含义更窄(因为该短语早于它)。


现在,真实的定义是:

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

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

此定义中需要注意的事项是:

  • 这里的“变量”是指调用者的(局部或全局)变量本身——即,如果我通过引用传递一个局部变量并赋值给它,我将更改调用者的变量本身,而不是例如,如果它是一个指针,它指向的任何内容。

    • 这现在被认为是不好的做法(作为隐式依赖关系)。因此,几乎所有较新的语言都是完全或几乎完全按值传递的。在函数不能返回多个值的语言中,按引用传递现在主要以“输出/输入参数”的形式使用。
  • 通过引用传递”中“引用”的含义。与一般“参考”术语的区别在于,这种“参考”是暂时的和隐含的。被调用者得到的是一个“变量”,它在某种程度上与原始变量“相同”。具体如何实现这种效果是无关紧要的(例如,语言还可能暴露一些实现细节——地址、指针、取消引用——这些都是无关紧要的;如果最终效果是这样的,那就是通过引用传递)。


现在,在现代语言中,变量往往是“引用类型”(另一个比“引用传递”更晚发明并受其启发的概念),即实际的对象数据被单独存储在某个地方(通常,在堆上),并且只有对它的“引用”被保存在变量中并作为参数传递。3

传递这样的引用属于按值传递,因为从技术上讲,变量的值是引用本身,而不是引用的对象。但是,对程序的净影响可以与按值传递或按引用传递相同:

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

正如你所看到的,这对技术与定义中的技术几乎相同,只是具有一定程度的间接性:只需将“variable”替换为“referenced object”即可。

它们没有商定的名称,这导致了扭曲的解释,例如“在值是引用的情况下按值调用”。1975年,芭芭拉·利斯科夫(Barbara Liskov)提出了“逐个对象共享”(或有时简称为“逐个共享调用”)这一术语,尽管它从未完全流行起来。此外,这两个短语都没有与原始对相提并论。难怪旧术语最终在没有更好的东西的情况下被重用,导致混乱。4

(对于新技术,我会使用术语“新”或“间接”按值传递/按引用传递


注意:很长一段时间以来,这个答案曾经说过:

假设我想与您共享一个网页。如果我告诉你 URL,我是 通过引用传递。您可以使用该 URL 查看相同的网页 I 可以看到。如果该页面发生更改,我们都会看到更改。如果你 删除 URL,您所做的只是破坏对该 URL 的引用 页面 - 您不会删除实际页面本身。

如果我打印出页面并给你打印输出,我就路过了 价值。您的页面是原始页面的断开连接的副本。你不会看到的 任何后续更改,以及您所做的任何更改(例如涂鸦 在您的打印输出上)不会显示在原始页面上。如果你 销毁打印输出,您已经销毁了 对象 - 但原始网页保持不变。

基本上是正确的,除了“引用”的狭义含义 - 它既是临时的,也是隐式的(它不必,但显式和/或持久性是附加功能,而不是按引用传递语义的一部分,如上所述)。一个更接近的类比是给你一份文档的副本,而不是邀请你处理原件。


1除非您使用 Fortran 或 Visual Basic 进行编程,否则这不是默认行为,而且在现代使用的大多数语言中,真正的引用调用甚至是不可能的。

2 相当多的老年人也支持它

3在几种现代语言中,所有类型都是引用类型。这种方法由 CLU 语言于 1975 年开创,此后被许多其他语言采用,包括 Python 和 Ruby。还有更多的语言使用混合方法,其中一些类型是“值类型”,而另一些是“引用类型”——其中包括 C#、Java 和 JavaScript。

4回收一个合适的旧术语本身并没有什么坏处但必须以某种方式明确每次使用的含义。不这样做正是令人困惑的原因。

评论

2赞 ICW 9/13/2019
您提供的“真实”定义并不是几乎每门入门编程课程中给出的定义。谷歌什么是通过引用传递,你不会得到那个答案。您提供的真实定义是对“引用”一词的误用,因为当您遵循该定义时,您使用的是别名而不是引用:您有两个变量实际上是相同的变量,即别名而不是引用。你的真实定义会无缘无故地造成大规模的混乱。只需说通过引用传递意味着传递地址。这是有道理的,可以避免这种毫无意义的混淆。
3赞 ivan_pozdeev 9/18/2019
@YungGun 1)请提供“几乎每门编程入门课程中给出的定义”的链接。还要注意的是,这旨在在今天的现实中明确,而不是在十年或三年前的现实中,当时编写了一些CS课程。2)“地址”不能在定义中使用,因为它故意从可能的实现中抽象出来。例如,某些语言(Fortran)没有指针;它们在是否向用户公开原始地址方面也有所不同(VB 没有);它也不必是原始内存地址,任何允许链接到变量的东西都可以。
1赞 ivan_pozdeev 10/1/2019
@YungGun“太久了,没读”。一眼就能看出答案中概述的困惑。通过引用传递是一种与实现无关的抽象技术。究竟在引擎盖下传递了什么并不重要,重要的是对程序的影响是什么。
1赞 Rafael Eyng 1/19/2020
“对程序的影响可以与按值传递或按引用传递相同”:我不同意效果与旧的“按引用传递”相同,因为调用方变量不能从被调用方内部重新分配
2赞 Rafael Eyng 1/19/2020
“芭芭拉·利斯科夫(Barbara Liskov)提出了”按对象调用共享“一词 - 如果这个名称指的是第一种或第二种技术,那就太好了。目前的文本没有说清楚
3赞 Stanley 1/24/2011 #8

如果不想在将原始变量传递到函数后更改其值,则应使用“按值传递”参数构造函数。

然后,该函数将只有值,而没有传入变量的地址。如果没有变量的地址,函数内部的代码就无法更改从函数外部看到的变量值。

但是,如果要使函数能够更改从外部看到的变量值,则需要使用引用传递。因为值和地址(引用)都被传入并在函数中可用。

1赞 abhinisha thakur 6/12/2013 #9

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

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

2赞 Monster 7/27/2014 #10

简而言之,按值传递就是它是什么,通过引用传递就是它所在的地方。

如果您的值是 VAR1 = “Happy Guy!”,则您只会看到 “Happy Guy!”。如果 VAR1 更改为“Happy Gal!”,您将不会知道。如果它通过引用传递,并且 VAR1 发生变化,您将这样做。

32赞 Than Skourtan 11/13/2014 #11

获取此信息的最简单方法是在 Excel 文件上。例如,假设您在单元格 A1 和 B1 中有两个数字 5 和 2,并且您想在第三个单元格中找到它们的总和,比如说 A2。

您可以通过两种方式执行此操作。

  • 通过在此单元格中键入 = 5 + 2 将其值传递给单元格 A2。在这种情况下,如果单元格 A1 或 B1 的值发生变化,则 A2 中的总和保持不变。

  • 或者通过键入 = A1 + B1 将单元格 A1 和 B1 的“引用”传递给单元格 A2在这种情况下,如果单元格 A1 或 B1 的值发生变化,则 A2 中的总和也会发生变化。

评论

1赞 Amit Ray 1/27/2018
这是所有其他答案中最简单和最好的例子。
0赞 BugShotGG 4/25/2015 #12

下面是一个示例,演示了按值传递 - 指针值 - 引用之间的差异:

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)的答案,该线程用通俗易懂的语言进行了解释。

评论

0赞 Chris Hunt 4/24/2017
你说“如果一个参数被声明为[作为引用],它对应的实际参数必须是一个变量”,但一般来说,情况并非如此。如果引用绑定到临时引用(例如函数的返回值),则其生存期将延长以匹配引用。有关详细信息,请参阅此处
9赞 user326964 12/28/2015 #13

按值传递 - 该函数复制变量并使用副本(因此它不会更改原始变量中的任何内容)

按引用传递 - 该函数使用原始变量。如果更改其他函数中的变量,则原始变量中的变量也会更改。

示例(复制和使用/自己尝试并查看):

#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;
}

保持简单,窥视。文字墙可能是一个坏习惯。

评论

0赞 Kevin Zhao 5/17/2016
这对于了解参数值是否更改非常有帮助,谢谢!
127赞 Daniel Pryden 1/24/2016 #14

这里的许多答案(尤其是投票最多的答案)在事实上是不正确的,因为它们误解了“引用调用”的真正含义。这是我试图把事情弄清楚的尝试。

TL;博士

简单来说:

  • 按值调用意味着将作为函数参数传递
  • 引用调用意味着将变量作为函数参数传递

用比喻的话来说:

  • 按值调用我在一张纸上写下一些东西并交给你的地方。也许它是一个 URL,也许它是《战争与和平》的完整副本。不管它是什么,它都在我给你的一张纸上,所以现在它实际上是你的一张纸。你现在可以自由地在那张纸上涂鸦,或者用那张纸在其他地方找到一些东西并摆弄它,随便什么。
  • 引用电话是指我给你我的笔记本,上面写着一些东西。你可以在我的笔记本上乱涂乱画(也许我希望你这样做,也许我不希望),然后我保留我的笔记本,上面写着你放在那里的乱涂乱画。此外,如果你或我在那里写的东西是关于如何在其他地方找到东西的信息,你或我都可以去那里摆弄这些信息。

“按值调用”和“按引用调用”是什么意思

请注意,这两个概念都完全独立于引用类型的概念(在 Java 中是 的所有类型,在 C# 中是所有类型的子类型)或指针类型的概念(在语义上等同于 Java 的“引用类型”,只是语法不同)。Objectclass

引用类型的概念对应于 URL:它本身既是一条信息,又是指向其他信息的引用(指,如果您愿意的话)。您可以在不同的地方拥有多个 URL 副本,并且它们不会更改它们都链接到的网站;如果网站已更新,则每个 URL 副本仍将指向更新的信息。相反,在任何一个位置更改 URL 都不会影响该 URL 的任何其他书面副本。

请注意,C++ 有一个“引用”的概念(例如),它不像 Java 和 C# 的“引用类型”,而是像“通过引用调用”。Java 和 C# 的“引用类型”以及 Python 中的所有类型都类似于 C 和 C++ 所说的“指针类型”(例如)。int&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是 的形式参数,也在第 1 行声明foo
  • arg是一个变量,具体来说是函数的局部变量,在第 2 行声明和初始化bar
  • arg也是对第 3 行的特定调用参数foo

这里有两组非常重要的概念需要区分。首先是变量

  • 是计算语言中表达式的结果。例如,在上面的函数中,在行之后,表达式的值为 。barint arg = 1;arg1
  • 变量值的容器。变量可以是可变的(这是大多数类C语言的默认值),只读(例如使用Java或C#声明)或深度不可变(例如使用C++的)。finalreadonlyconst

要区分的另一对重要概念是参数与参数

  • 参数(也称为形式参数)是调用函数时必须由调用方提供的变量
  • 参数是由函数的调用方提供的,用于满足该函数的特定形式参数

按值调用

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

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

int arg = 1;
int another_variable = arg;

这里是完全独立的变量——它们的值可以相互独立地变化。但是,在声明的位置,它被初始化为保存相同的值,即 .arganother_variableanother_variablearg1

由于它们是自变量,因此更改不会影响:another_variablearg

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

也就是说,按值调用的含义的定义特征是,被调用方(在本例中)接收值作为参数,但对于这些值,这些调用方的变量(在本例中)具有自己的独立变量foobar

回到我上面的比喻,如果我是,你是,当我打电话给你时,我会递给你一张纸,上面写着一个值。你称那张纸为.该值是我在笔记本中写入的值(局部变量)的副本,在我调用的变量中。barfooparamarg

(顺便说一句:根据硬件和操作系统的不同,关于如何从一个函数调用另一个函数,有各种调用约定。召唤惯例就像我们决定是否将值写在一张纸上,然后交给你,或者你有一张纸,我写在上面,或者我把它写在我们俩面前的墙上。这也是一个有趣的话题,但远远超出了这个已经很长的答案的范围。

按引用调用

引用调用中,函数的形式参数只是调用方作为参数提供的相同变量的新名称

回到上面的例子,它等价于:

// 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

由于只是 的另一个名称 -- 也就是说,它们是相同的变量,因此 的更改反映在 中。这是按引用调用与按值调用不同的根本方式。paramargparamarg

很少有语言支持引用调用,但 C++ 可以这样做:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

在这种情况下,它不仅具有与 相同的,而且实际上只是名称不同),因此可以观察到它已递增。paramargargbararg

请注意,这不是任何 Java、JavaScript、C、Objective-C、Python 或当今几乎任何其他流行语言的工作方式。这意味着这些语言不是通过引用调用的,而是通过值调用的。

附录:通过对象共享调用

如果你所拥有的是按值调用的,但实际值是引用类型或指针类型,那么“值”本身就不是很有趣(例如,在 C 中,它只是一个特定于平台大小的整数)——有趣的是该值指向什么。

如果该引用类型(即指针)指向的内容是可变的,则可能会产生一个有趣的效果:您可以修改指向值,并且调用方可以观察到指向值的更改,即使调用方无法观察到指针本身的更改。

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

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

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

评论

0赞 S.K. Venkat 7/6/2016
解释得更好:这里有两组非常重要的概念需要区分。The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
7赞 drlolly 5/5/2017
很好的答案。我想我要补充一点,不需要通过引用创建新的存储。参数名称引用原始存储(内存)。谢谢
77赞 mfaani 3/25/2016 #15

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

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

所以如果你说,要知道有两件事。它的值以及它在内存中的位置,即一些十六进制数,可能如下所示:.employee.name = "John"name"John"0x7fd5d258dd00

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

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

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

有关更多信息,我建议您阅读有关取消引用指针的信息,以及为什么选择结构(值类型)而不是类(引用类型)。

评论

4赞 Haisum Usman 1/28/2017
这就是我一直在寻找的,实际上应该寻找概念而不仅仅是解释,竖起大拇指兄弟。
1赞 chetan 5/11/2020
Java 始终按值传递。在 java 中传递对对象的引用被视为按值传递。这与你的说法相矛盾,“传递0x7fd5d258dd00被称为通过引用传递。
0赞 Wolfson 9/17/2020
仅仅区分 和 是不够的。问题是新内存是否用于您传递的任何内容。您可以传递基于按值传递(被调用方地址的新存储),以便在被调用方中更改此地址不会影响调用方的原始变量(旧存储),该变量仍保留原始地址。valueaddressaddress
0赞 Peter Mortensen 8/16/2022
C++ 标准是否说明了内存和内存地址?这难道不依赖于实现吗?
0赞 mfaani 8/17/2022
对不起,芽。我没有 c++ 经验。
1赞 Misaal D'souza 1/28/2021 #16

1. 按值传递/按值调用

   void printvalue(int x) 
   {
       x = x + 1 ;
       cout << x ;  // 6
   }

   int x = 5;
   printvalue(x);
   cout << x;    // 5

在按值调用中,当您将一个值传递给 即 的参数时,它将被复制到 。现在,我们有两个不同的值和复制的值,这两个值存储在不同的内存位置。因此,如果你在内部进行任何更改,它不会反映到论点中。printvalue(x)5void printvalue(int x)55void 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* xprintvalue(&x)

0赞 sifr_dot_in 8/14/2021 #17

问题是“vs”。

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

在与引用传递时,不会为这些值占用额外的内存(在这种情况下内存效率高)。

评论

0赞 Peter Mortensen 8/16/2022
但只是临时的额外内存(在堆栈上)?
0赞 Peter Mortensen 8/16/2022
你说的“vs”是什么意思(在这种情况下)?你能详细说明一下吗?
0赞 sifr_dot_in 8/17/2022
@PeterMortensen“vs”可以被视为反对或并排比较。
0赞 sifr_dot_in 8/17/2022
@PeterMortensen,如果你说“但只是暂时的......”,这里应该有“额外的内存”点。
15赞 Hamed Naeemaei 12/11/2021 #18

看看这张照片:

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

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

要阅读本文,请参阅此链接

Call by reference vs call by value