网上有太多这样的无效讨论了,像「Java 是值传递还是引用传递?」,「Python 传可变对象就是引用传递,传不可变对象就是值传递!」、「一切都是值传递!」的话题真的是起码一个月来一次。
什么是值传递和引用传递?
传引用调用、传值调用是计算机科学里面的求值策略。求值策略定义何时和以何种次序求值给函数的实际参数,什么时候把它们代换入函数,和代换以何种形式发生。
- 值传递意味着传 值 作为参数。
- 引用传递意味着传 变量 作为参数。
注意这里的引用传递,它和引用类型毫无关系,更和 Java 和 Python 的引用类型没有关系( Java 之类的引用类型更像是 C/C++ 里面的指针)。C++ 里面的类似别名一样的引用类型,和 Java 、Python 的引用类型不同,它有时候在传递过程中符合引用调用的特点。
函数调用
要清楚为什么,就得先明白是什么。
以 C++ 为例:
void func(int x){
x++;
}
int main(){
int test = 2;
func(test);
return 0;
}
func
是我们调用的函数。test
是一个变量,同时是func
的实际参数 ( argument )。x
也是变量,而且是局部变量,同时是func
的形式参数 ( parameter )。- 实际参数是值,它可以由字面量值或者变量提供。
- 形式参数是变量,它只能是变量。
main
是调用者 ( caller )。func
是被调用者 ( callee )。
传值调用 Call by value
值传递策略中,一旦开始函数调用过程,形式参数就会以实际参数的值初始化,且二者互不影响。
相当于我有一份 doc 文档,复制了一份给你。你怎么修改你那一份,对我的文档都不会有影响。
如果是值传递,第一段代码相当于:
int test = 2;
int x = test;
x++;
C 语言是传值调用的。
传引用调用 Call by reference
引用传递策略中,函数调用将形式参数就是实际参数的别名,二者是同一个变量。
相当于我有一份 doc 文档,你修改这份文档,对我来说是可见的。
如果是引用传递,第一段代码相当于:
int test = 2;
test++;
传共享对象调用 Call by sharing
传值、传引用已经不适用于现代编程语言,现在编程语言一般是传入一个特殊的引用类型的值,相当实际参数复制了一份地址给形式参数,但我们可以根据这个地址去修改对象,使得该函数之外的作为实际参数的变量也会发生改变。
1974 年,Barbara Liskov 意识到自己的 CLU 语言不是二者的任一种,于是命名了传共享对象调用。Python、Java、JavaScript、Scheme、OCaml 等语言都使用了传共享对象调用。(Evaluation strategy – Wikiwand)
传共享对象调用中,函数传递的是一个可以共享的对象,这样就可以达到引用调用的效果:一旦被调用者修改了对象,调用者就可以看到变化。
如果坚持要一分为二,那么现在的 Java、Python 都是传值调用,只不过传的是一个可以被函数改变的对象。例如 Python:
def func(alist):
alist.append(1)
alist = [0]
def main():
src = []
func(src)
print(src)
if __name__ == '__main__':
main()
上面代码会打印出 [1]
,因为列表是可变对象,append
方法改变了 alist
。而赋值局部变量的 alist = [0]
对函数调用之外的作用域没有影响。
像 Java 也是这样:
class Test {
public void addBrand(String[] book) {
book[0] = "0";
book = new String[]{"1"};
}
public static void main(String[] args) {
Test test = new Test();
String[] sci_book = new String[1];
test.addBrand(sci_book);
out.println(Arrays.toString(sci_book));
}
}
打印出 [0]
而非 [1]
。
在这类语言中赋值是给变量绑定一个新对象,而不是改变对象。
等我考完试接着写!