首先,在CS理论中定义的“按值传递与按引用传递”的区别现在已经过时,因为最初定义为“按引用传递”的技术已经失宠,现在很少使用。1
较新的语言2倾向于使用不同(但相似)的一对技术来实现相同的效果(见下文),这是造成混淆的主要原因。
造成混淆的第二个原因是,在“通过引用”中,“引用”的含义比一般术语“引用”的含义要窄(因为该短语早于它)。
现在,真正的定义是:
>
通过引用传递参数时,调用方和被调用方对该参数使用相同的变量。如果被调用方修改了参数变量,则该效果对调用方的变量是可见的。
当参数按值传递时,调用方和被调用方有两个值相同的独立变量。如果被调用方修改了参数变量,则该效果对调用方不可见。
在这个定义中需要注意的是:
>
“variable”在这里表示调用方的(局部或全局)变量本身--即,如果我通过引用传递一个局部变量并赋值给它,我将更改调用方的变量本身,而不是例如,如果它是指针,则更改它所指向的任何东西。
“比照而过”中“比照”的含义。与一般的“指代”术语不同的是,这个“指代”是暂时的、隐含的。被调用者基本上得到的是一个“变量”,它与原始变量有某种“相同”。具体如何实现这种效果是不相关的(例如,该语言还可能公开一些实现细节--地址、指针、取消引用--这都是不相关的;如果净效果是这样的话,它就是按引用传递)。
现在,在现代语言中,变量倾向于“引用类型”(另一个比“按引用传递”更晚的概念,并受其启发),即实际对象数据被单独存储在某个地方(通常在堆上),并且只有对它的“引用”才会保存在变量中并作为参数传递。3
传递这样的引用属于按值传递,因为从技术上讲,变量的值是引用本身,而不是被引用的对象。然而,对程序的净影响可以与按值传递或按引用传递相同:
正如您可能看到的,这对技术几乎与定义中的技术相同,只是有一个间接级别:只需将“变量”替换为“引用对象”。
它们没有一个一致的名称,这导致了扭曲的解释,比如“在值是引用的情况下按值调用”。1975年,Barbara Liskov提出了“按对象调用共享”这个术语(或者有时只是“按共享调用”),尽管它从未流行起来。此外,这两个词组都不与原词组平行。难怪旧的术语在没有更好的东西的情况下被重复使用,导致混乱。4
注:很长一段时间以来,这个答案习惯说:
说我想和你分享一个网页。如果我告诉你网址,我是通过引用。您可以使用该URL来查看与我所看到的相同的网页。如果那一页被改变了,我们都看到了改变。如果您删除了URL,您所做的只是破坏了对该页面的引用--您并不是删除了实际的页面本身。
如果我打印出这一页并给你打印出来的,我就是在传递值。您的页面是原始页面的断开副本。您不会看到任何后续更改,并且您所做的任何更改(例如,在打印输出上涂鸦)都不会显示在原始页面上。如果您销毁了打印输出,那么实际上您已经销毁了对象的副本--但原始网页仍然保持完整。
这基本上是正确的,但“引用”的狭义含义除外--它既是临时的,也是隐式的(它不是必须的,但是显式和/或持久化是附加的特性,不是前面解释的通过引用语义的一部分)。一个更接近的类比就是给你一份文档的副本,而不是邀请你去做文档的原件。
1除非使用Fortran或Visual Basic编程,否则这不是默认行为,而且在现代使用的大多数语言中,甚至不可能真正的按引用调用。
2相当多的老版本也支持它
3在几种现代语言中,所有类型都是引用类型。这种方法在1975年由CLU语言首创,后来被许多其他语言采用,包括Python和Ruby。更多的语言使用混合方法,其中一些类型是“值类型”,另一些是“引用类型”--其中包括C#、Java和JavaScript。
4重复使用一个合适的旧术语本身并没有什么不好,但是你必须弄清楚每次使用的是什么意思。不这样做恰恰会造成混乱。