关于语言不可知:通过引用传递与传递值之间有什么区别?

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

两者有什么区别

  • 通过引用传递的参数
  • 通过值传递的参数?
  • 请给我举几个例子好吗?


    首先,CS理论中定义的"传递值与传递参考"的区别现在已经过时,因为最初定义为"传递参考"的技术已经不再受欢迎,现在很少使用。1好的。

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

    混淆的第二个来源是,在"引用传递"中,"引用"的含义比一般术语"引用"的含义要窄(因为短语比它早)。好的。

    现在,真正的定义是:好的。

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

    • 当一个参数通过值传递时,调用者和被调用者有两个值相同的独立变量。如果被调用方修改了参数变量,则调用方看不到该效果。好的。

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

    • 这里的"变量"是指调用者的(本地或全局)变量本身——也就是说,如果我通过引用传递一个局部变量并给它赋值,我将更改调用者的变量本身,而不是它指向的任何对象(如果它是指针)。好的。

      • 这现在被认为是不好的做法(作为隐式依赖)。因此,几乎所有较新的语言都是专有的,或者几乎都是超值的。pass-by引用现在主要以"output/inout参数"的形式在函数不能返回多个值的语言中使用。
    • "旁路参考"中"参考"的含义。与一般的"参考"术语不同的是,这个"参考"是暂时的和隐含的。被叫方基本上得到的是一个"变量",它在某种程度上与原始变量"相同"。这种效果的具体实现方式是不相关的(例如,语言还可能公开一些实现细节——地址、指针、取消引用——这都是不相关的;如果净效果是这样的,它是通过引用传递的)。好的。

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

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

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

    如您所见,这两种技术几乎与定义中的技术相同,只是具有间接性:只需将"variable"替换为"referenced object"。好的。

    他们没有约定的名字,这导致了扭曲的解释,比如"按值调用,值是引用"。1975年,BarbaraLiskov提出了"按对象共享调用"(有时也只是"按共享调用")这个词,尽管它从未流行过。而且,这两个短语都没有与原来的一对平行。难怪旧术语最终被重复使用,而没有更好的东西,导致混乱。好的。

    注:长期以来,这个答案常说:好的。

    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.

    Ok.

    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.

    Ok.

    这基本上是正确的,除了"引用"的狭义之外——它是临时的和隐式的(它不必如此,但显式和/或持久是额外的特性,而不是上述逐引用语义的一部分)。一个更接近的类比是给你一份文件的副本,而不是邀请你做原件。好的。

    1好的。

    2a fair amount of older ones support it,too好的。

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

    4从本质上来说,回收一个合适的旧术语没有什么不好的,但是你必须以某种方式让它明白每次使用的是什么意思。不这样做正是导致混乱的原因。好的。好啊。


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

    • Java只支持传递值。始终复制参数,即使在复制对对象的引用时,被调用函数中的参数将指向同一对象,调用方将看到对该对象的更改。因为这可能会让人困惑,所以乔恩·斯基特必须这么说。
    • C支持传递值和传递引用(在调用方和被调用函数中使用关键字ref)。乔恩·斯基特对此也有很好的解释。
    • C++支持传递值和按引用传递(引用函数在调用函数中使用的类型)。您将在下面找到对此的解释。

    代码

    因为我的语言是C++,所以我会在这里使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    // 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中的一个例子不会伤害:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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://javaude.com/articles/passbyvalue.htm网站


    这里的许多答案(尤其是最乐观的答案)事实上是错误的,因为他们误解了"参考呼叫"的真正含义。这是我试图澄清的问题。好的。DR

    简单来说:好的。

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

    用隐喻的术语来说:好的。

    • "按值调用"是指我在一张纸上写下一些东西,然后交给你。也许是一个网址,也许是战争与和平的完整副本。不管它是什么,它在我给你的一张纸上,所以现在它实际上是你的一张纸。你现在可以自由地在那张纸上乱涂乱画,或者用那张纸在别的地方找点东西来摆弄它。
    • "参考呼叫"是指我把我的笔记本给你的时候,笔记本上写着一些东西。你可以在我的笔记本上涂鸦(也许我想让你,也许我不想),然后我就把我的笔记本和你放在那里的任何涂鸦放在一起。另外,如果你或我写的有关于如何在其他地方找到东西的信息,你或我可以去那里处理这些信息。

    什么"按值调用"和"参照调用"不是指

    请注意,这两个概念都完全独立于引用类型的概念(Java中的所有类型都是EDCOX1的0个类型的子类型,而C中所有的EDCOX1都是1个类型),或者C中的指针类型的概念(在语义上等同于Java的"引用类型",简单地用不同的语法)。好的。

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

    注意C++有一个"引用"(例如EDCOX1(2))的概念,它不像Java和C的"引用类型",而是类似于"按引用调用"。Java和C的"引用类型"和Python中的所有类型都类似于C和C++所称的"指针类型"(例如EDCOX1(3))。好的。

    好吧,这是更长更正式的解释。好的。术语

    首先,我想强调一些重要的术语,帮助澄清我的答案,并确保我们在使用词语时都引用相同的观点。(在实践中,我认为,关于这些主题的绝大多数混淆源于以不完全传达预期含义的方式使用词语。)好的。

    首先,下面是一些C语言的函数声明示例:好的。

    1
    2
    3
    void foo(int param) {  // line 1
      param += 1;
    }

    下面是一个调用这个函数的例子:好的。

    1
    2
    3
    4
    void bar() {
      int arg = 1;  // line 2
      foo(arg);     // line 3
    }

    使用这个例子,我想定义一些重要的术语:好的。

    • EDCOX1×0是一个在第1行上声明的函数(Java坚持所有函数的方法,但是这个概念是一样的,没有一般性的损失;C和C++在声明和定义之间做了区分,这里我不这么说。
    • paramfoo的形式参数,也在第1行声明。
    • arg是在第2行声明和初始化的变量,特别是函数bar的局部变量。
    • arg也是第3行对foo的特定调用的一个参数。

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

    • 值是评估语言中表达式的结果。例如,在上面的bar函数中,在行int arg = 1;之后,表达式arg的值为1
    • 变量是值的容器。变量可以是可变的(这是大多数C类语言中的默认值),只读(例如,用Java的EDCOX1引用11或C的EDCOX1(12)表示)或深度不变(例如使用C++的EDCOX1×13)。

    要区分的另一个重要概念是参数与参数:好的。

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

    按值调用

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

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

    1
    2
    int arg = 1;
    int another_variable = arg;

    这里,arganother_variable是完全独立的变量——它们的值可以彼此独立地改变。但是,在声明another_variable时,它被初始化为与arg所持有的值相同,即1。好的。

    由于它们是独立变量,因此对another_variable的更改不影响arg:好的。

    1
    2
    3
    4
    5
    6
    int arg = 1;
    int another_variable = arg;
    another_variable = 2;

    assert arg == 1; // true
    assert another_variable == 2; // true

    这与上面的示例中的argparam之间的关系完全相同,我将在这里重复对称:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    void foo(int param) {
      param += 1;
    }

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

    就好像我们是这样写代码的:好的。

    1
    2
    3
    4
    5
    6
    7
    // 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的变量中。好的。

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

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

    回到上面的例子,它相当于:好的。

    1
    2
    3
    4
    5
    6
    7
    // 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++可以这样做:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    void foo(int& param) {
      param += 1;
    }

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

    在这种情况下,param的值不仅与arg的值相同,而且实际上是arg的值(只是用一个不同的名字),因此bar可以观察到arg已经增加。好的。

    注意,这不是Java、JavaScript、C、Objy-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"的值。

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


    下面是一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #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传递时,基本上就是向变量传递指针。传递值传递变量的副本。在基本用法中,这通常意味着对变量的pass-by-ref更改将被视为调用方法,而它们不会被视为pass-by值。


    传递值发送存储在指定变量中的数据的副本,传递引用将直接链接发送到变量本身。因此,如果您通过引用传递一个变量,然后更改传递给它的块中的变量,那么原始变量将被更改。如果简单地传递by value,原始变量将不能被传递给它的块所更改,但是您将获得调用时包含的任何变量的副本。


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

    传递引用-函数使用原始变量,如果在另一个函数中更改该变量,它也会在原始变量中更改。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #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讨论和示例链接文本


    实例:

    1
    2
    3
    4
    5
    6
    class Dog
    {
    public:
        barkAt( const std::string& pOtherDog ); // const reference
        barkAt( std::string pOtherDog ); // value
    };

    一般来说,const &是最好的。你不会招致建造和破坏的处罚。如果引用不是常量,那么您的接口建议它将更改传入的数据。


    简而言之,传递值就是它本身,引用传递值就是它所在的位置。

    如果你的价值是var1="快乐的家伙!"你只会看到"快乐的家伙!"。如果var1改为"快乐女孩!"你不会知道的。如果它是通过引用传递的,并且var1发生了更改,那么您将得到。


    传递值是指如何通过使用参数将值传递给函数。在传递值中,我们复制存储在指定变量中的数据,它比传递引用bcse t慢。数据被复制。我们对复制的数据进行更改,原始数据不受影响。在pass-by引用或pass-by地址中,我们向变量本身发送直接链接。或将指针传递给变量。速度更快,因为消耗的时间更少


    如果不希望在将原始变量传递给函数后更改其值,则应使用"传递值"参数构造该函数。

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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
    }

    "参照传递"方法有一个重要的局限性。如果参数声明为通过引用传递(因此前面加上&号),则其对应的实际参数必须是变量。

    引用"传递值"形参的实际参数通常可以是表达式,因此它不仅可以使用变量,还可以使用文本,甚至是函数调用的结果。

    函数无法将值放入变量之外的其他内容。它不能将新值赋给文本或强制表达式更改其结果。

    附言:你也可以在用简单的语言解释它的当前线程中检查DylanBeattie的答案。