首页 文章

传递参考与传递值之间有什么区别?

提问于
浏览
469

有什么区别

  • 通过引用传递的参数

  • 一个按值传递的参数?

你能给我一些例子吗?

16 回答

  • 124

    首先, the "pass by value vs. pass by reference" distinction as defined in the CS theory is now obsolete 因为 the technique originally defined as "pass by reference" has since fallen out of favor 而现在很少使用

    Newer languages2 tend to use a different (but similar) pair of techniques to achieve the same effects (见下文)这是混淆的主要来源 .

    混淆的第二个原因是 in "pass by reference", "reference" has a narrower meaning than the general term "reference" (因为这个短语早于它) .


    现在,真实的定义是:

    • 当参数为 passed by reference 时,调用者和被调用者 use the same variable 为参数 . 如果被调用者修改了参数变量,则调用者的变量可以看到该效果 .

    • 当参数为 passed by value 时,调用者和被调用者具有相同值的 two independent variables . 如果被调用者修改了参数变量,则调用者看不到该效果 .

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

    • "Variable" here means the caller's (local or global) variable itself - 即如果我通过引用传递一个局部变量并赋值给它,那么我自己就是变量,而不是无论它指向什么,如果它是一个指针 .

    • 现在这被认为是不好的做法(作为隐式依赖) . 因此, virtually all newer languages are exclusively, or almost exclusively pass-by-value. Pass-by-reference现在主要以"output/inout arguments"的形式用于函数不能返回多个值的语言中 .

    • The meaning of "reference" in "pass by reference" . 与一般"reference"术语的区别在于 this "reference" is temporary and implicit. 被调用者基本上得到的是 a "variable" that is somehow "the same" as the original one. 这种效果实现的具体方式是无关紧要的(例如,语言也可能暴露一些实现细节 - 地址,指针,解除引用 - 这都是无关紧要的;如果净效应是这个,那就是传递参考) .


    Now, in modern languages, variables tend to be of "reference types" (另一个概念发明于"pass by reference"并受其启发),即实际的对象数据存储在某处(通常在堆上),并且只有"references"才能保存在变量中并作为参数传递 .

    Passing such a reference falls under pass-by-value 因为变量的值在技术上是引用本身,而不是引用的对象 . 但是, the net effect on the program can be the same as either pass-by-value or pass-by-reference:

    • 如果引用只是从调用者的变量中获取并作为参数传递,则它与pass-by-reference具有相同的效果:如果被调用的对象在被调用者中发生变异,则调用者将看到更改 .

    • 但是,如果重新保持包含此引用的变量,它将停止指向该对象,因此对该变量的任何进一步操作都将影响它现在指向的任何内容 .

    • 要获得与传值相同的效果,可以在某个时刻创建对象的副本 . 选项包括:

    • 调用者可以在调用之前创建一个私有副本,并为被调用者提供一个引用 .

    • 在某些语言中,某些对象类型是"immutable":对它们的任何操作似乎改变了值实际上会创建一个全新的对象而不会影响原始对象 . 因此,传递此类型的对象作为参数始终具有传值的效果:如果需要更改,则会自动生成被调用者的副本,并且调用方的对象永远不会受到影响 .

    • 在函数式语言中,所有对象都是不可变的 .

    如您所见, this pair of techniques is almost the same as those in the definition, only with a level of indirection: just replace "variable" with "referenced object".

    他们没有达成一致的名称,导致像"call by value where the value is a reference"这样的扭曲解释 . 1975年,Barbara Liskov建议使用“call-by-object-sharing " (or sometimes just "呼叫共享”这一术语,尽管它从未完全流行起来 . 而且,这些短语都没有与原始对并行 . 难怪旧条款最终被重复使用,导致混乱


    NOTE :很长一段时间,这个答案常说:

    说我想和你分享一个网页 . 如果我告诉你URL,我通过引用传递 . 您可以使用该URL查看我可以看到的同一网页 . 如果该页面被更改,我们都会看到更改 . 如果您删除了URL,那么您所做的就是销毁对该页面的引用 - 您不会删除实际页面本身 . 如果我打印出页面并给你打印输出,我就会超过 Value . 您的页面是原始的断开连接的副本 . 您不会看到任何后续更改,并且您所做的任何更改(例如,在您的打印输出上涂写)都不会显示在原始页面上 . 如果您销毁打印输出,实际上已经销毁了对象的副本 - 但原始网页保持不变 .

    除了"reference"的较窄含义之外,这大部分都是正确的 - 它既是临时的又是隐含的(它不是必须的,但是显式和/或持久是附加特征,而不是传递引用语义的一部分,如上面解释过) . 更接近的类比是给你一份文件副本,邀请你去处理原文 .


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

    2A相当数量的老年人也支持它

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

    4回收一个合适的旧术语本身没有什么不好,但是必须以某种方式明确每次使用哪个含义 . 不这样做正是导致混乱的原因 .

  • 977

    这是一种如何将参数传递给函数的方法 . 通过引用传递意味着被调用函数的参数将与调用者的传递参数相同(不是值,而是标识 - 变量本身) . 按值传递意味着被调用函数的参数将是调用者传递的参数的副本 . 值将是相同的,但身份 - 变量 - 是不同的 . 因此,在一种情况下由被调用函数完成的参数的改变改变了传递的参数,而在另一种情况下,仅改变被调用函数中的参数的值(这只是一个拷贝) . 快点:

    • Java仅支持按值传递 . 始终复制参数,即使复制对象的引用时,被调用函数中的参数也将指向同一对象,并且将在调用者中看到对该对象的更改 . 由于这可能令人困惑,所以Jon Skeet对此有所说明 .

    • C#支持按值传递并按引用传递(在调用者和被调用函数中使用关键字 ref ) . Jon Skeet对这个here有一个很好的解释 .

    • 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

    这里的许多答案(特别是最受欢迎的答案)实际上是不正确的,因为他们误解了“通过引用呼叫”的真正含义 . 这是我试图解决问题的尝试 .

    TL; DR

    最简单的说法:

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

    • 引用调用意味着您将 variables 作为函数参数传递

    用比喻来说:

    • Call by valueI write down something on a piece of paper and hand it to you 的地方 . 也许它是战争与和平的完整副本 . 它无论是什么,都给了你,所以现在它实际上是 your piece of paper . 你现在可以自由地在那张纸上涂鸦,或者使用那张纸在其他地方找到一些东西并随便弄乱,无论如何 .

    • Call by referenceI give you my notebook which has something written down in it 的时候 . 你可以在我的笔记本上乱涂乱画(也许我想要你,也许我不会放在那里 . 而且,如果你或我写的是关于如何在其他地方找到某些东西的信息,要么你或我可以去那里小提琴有了这些信息 .

    “按 Value 呼叫”和“按参考呼叫”并不意味着什么

    请注意,这两个概念都是完全独立的,并且与 reference types 的概念完全正交(在Java中,所有类型都是 Object 的子类型,在C#中所有 class 类型),或者在C语言中的 pointer types 概念(在语义上等效)到Java的"reference types",只是使用不同的语法) .

    引用类型的概念对应于URL:它本身就是一条信息,它是一个引用(如果你愿意的话,指针)到其他信息 . 您可以在不同的位置拥有多个URL副本,但它们不会影响该URL的任何其他书面副本 .

    请注意,C的概念是"references"(例如 int& ),它是 not ,如Java和C#的"reference types",但 is 如"call by reference" . Java和C#的"reference types"以及Python中的所有类型都类似于C和C调用"pointer types"(例如 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区分声明和定义,我不会在这里讨论)

    • paramfoo 的形式参数,也在第1行声明

    • arg 是一个变量,特别是函数 bar 的局部变量,在第2行宣布并初始化

    • arg 也是第3行 foo 的特定调用的参数

    这里有两个非常重要的概念要区分 . 第一个是 Value 与变量:

    • A value 是该语言中的 result of evaluating an expression . 例如,在上面的 bar 函数中,在行 int arg = 1; 之后,表达式 arg 的值为 1 .

    • A variablecontainer for values . 变量可以是可变的(这是大多数类C语言中的默认值),只读(例如使用Java的 final 或C#的 readonly 声明)或深度不可变(例如使用C的 const ) .

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

    • A parameter (也称为形式参数)是调用函数时必须由调用者提供的变量 .

    • argument 是函数调用者提供的值,用于满足该函数的特定形式参数

    按值调用

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

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

    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 . 该值是我在笔记本中写入的值(我的局部变量)的 copy ,在我调用 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 的另一个名称 - 即它们是 the same variable ,因此 param 的更改将反映在 arg 中 . 这是通过引用调用与按值调用不同的基本方式 .

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

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

    在这种情况下, param 不仅具有与 arg 相同的值,它实际上是 is arg (仅通过不同的名称),因此 bar 可以观察到 arg 已经递增 .

    请注意,这是 not 如何使用Java,JavaScript,C,Objective-C,Python或几乎任何其他流行语言 . 这意味着这些语言是通过引用调用的,它们是按值调用的 .

    附录:通过对象共享进行调用

    如果您拥有的是按值调用,但实际值是引用类型或指针类型,则"value"本身不是平台特定大小的整数) - 有趣的是该值 points to .

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

    再次借用URL的类比,如果我们关心的是网站而不是URL,那么我向网站提供了一个 copy 网址的事实并不是特别有趣 . 你在你的URL副本上涂鸦的事实并不是我们关心的事情(实际上,在Java和Python这样的语言中,"URL"或参考类型值根本无法修改,只有指向的东西它可以) .

    Barbara Liskov,当她发明了CLU编程语言(具有这些语义)时,就意识到了现有的术语"call by value"和"call by reference"对于描述这种新语言的语义并不是特别有用 . 所以她发明了一个新名词:call by object sharing .

    在讨论技术上按值调用的语言时,但是使用的常见类型是引用或指针类型(即:几乎所有现代命令式,面向对象或多范式编程语言),我发现它不会让人感到困惑 . 简单地避免谈论 Value 呼叫或通过参考呼叫 . 坚持 call by object sharing (或简称 call by object ),没有人会感到困惑 . :-)

  • 58

    这是一个例子:

    #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

    在理解2个术语之前,您了解以下内容 . 每个对象都有两件可以区分的东西 .

    • 它的 Value .

    • 它的地址 .

    所以,如果你说 employee.name = "John"

    知道 name 有两件事 . 它的值 "John" 以及它在内存中的位置是一些十六进制数可能是这样的: 0x7fd5d258dd00 .

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

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

    有关这方面的更多信息,我建议您阅读dereferencing a pointer以及why choose struct (value type) over class (reference type)

  • 9

    获取此信息的最简单方法是在Excel文件上 . 例如,假设您在单元格A1和B1中有两个数字5和2,并且您希望在第三个单元格中找到它们的总和,假设为A2 . 你可以用两种方式做到这一点 .

    • 通过 passing their values to cell A2 键入= 5 2进入此单元格 . 在这种情况下,如果单元格A1或B1的值改变,则A2中的总和保持不变 .

    • 或按 passing the “references” of the cells A1 and B1 to cell A2 键入= A1 B1 . 在这种情况下,如果单元格A1或B1的值改变,则A2中的总和也改变 .

  • 1

    当你通过ref传递时,你基本上是传递一个指向变量的指针 . 通过值传递您传递变量的副本 . 在基本用法中,这通常意味着传递给变量的变量将被看作是调用方法并且通过值传递它们不会 .

  • 18

    按值传递存储在您指定的变量中的数据的COPY,通过引用传递直接链接到变量本身 . 因此,如果您通过引用传递变量然后更改传递给它的块内的变量,则原始变量将被更改 . 如果您只是按值传递,原始变量将无法通过您传入的块更改,但您将获得调用时包含的任何内容的副本 .

  • 52

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

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

    请参阅此处的c#讨论和示例link text

  • 3

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

    经过reference - 该函数使用原始变量,如果更改另一个函数中的变量,它也会更改原始变量 .

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

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

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

  • 4

    例子:

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

    const & 通常是最好的 . 你没有't incur the construction and destruction penalty. If the reference isn't const你的界面建议它会改变传入的数据 .

  • 2

    简而言之,Passed by value是什么,并通过引用传递它是什么 .

    如果你的值是VAR1 =“快乐的家伙!”,你只会看到“快乐的家伙!” . 如果VAR1变为“Happy Gal!”,你就不会知道 . 如果它通过引用传递,并且VAR1更改,您将 .

  • 22

    按值传递意味着如何通过使用参数将值传递给函数 . 在pass by value中,我们复制存储在我们指定的变量中的数据,并且比复制数据时通过引用bcse更慢 . 我们对复制数据进行更改,原始数据不受影响 . 通过推荐传递或通过地址传递我们发送直接链接到变量本身 . 或者将指针传递给变量 . 它的消耗时间更短

  • 5

    如果您不想在将原始变量传递给函数后更改原始变量的值,则应使用“ pass by value ”参数构造该函数 .

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

    但是如果你想让函数能够改变从外部看到的变量值,你需要使用 pass by reference . 因为值和地址(引用)都在函数内传递并可用 .

  • 45

    这是一个演示 pass by value - pointer value - reference 之间差异的示例:

    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
    }
    

    “通过引用传递”方法具有 an important limitation . 如果参数声明为 passed by reference (因此它以&符号开头),则其对应的 actual parameter must be a variable .

    引用“通过值传递”形式参数的实际参数通常可以是 an expression ,因此不仅可以使用变量,还可以使用文字甚至函数调用的结果 .

    该函数无法将值放在变量以外的值中 . 它不能为文字指定新值或强制表达式更改其结果 .

    PS:你也可以用当前的帖子检查Dylan Beattie的答案,用简单的词语解释它 .

  • -1

    这是链接检查这个:Pass by value VS Pass by reference

    enter image description here

相关问题