按值传递和按引用传递
简介:按值传递和按引用传递有什么区别?
关于这两个概念,其实主要来自于程序设计中一个叫“求值策略”的概念。这个概念在百度百科中可以查到:
百度百科上这段话的说辞上很绕很拗口,这块不用纠结,只要知道一点:这个求值策略下面分两大类:严格的和非严格的;其中严格的下面就有“值传递、引用传递”的概念介绍。
根据该词条的介绍可以发现其实关于求值策略的具体方式有很多中,很多其他的可能压根儿都没有听说过,这里可以忽略掉,但是我在查阅过程中看到了三个比较熟悉的实现方式:传值调用、传引用调用、传共享对象调用。
这里面所谓的传值调用其实就是我们常说的值传递,传引用调用就是我们说的引用传递,不过多出了一个传共享对象调用。
传值调用
这种模式我们熟悉的C语言就在使用,这种模式下,实际参数被求出的值是被绑定到了被调函数中对应的变量上,通常都是把整个数据copy一份到新的内存区域中做副本,这时候被调函数内部如果对这个变量进行了调整,也只是在copy后的副本区域上进行了调整,不会影响源参数。
传引用调用
这种模式下,传入到函数内的是实际参数的隐式引用,而并非把整个实际参数的内容全部拷贝到新的内存区域上;如果在被调函数内部对变量进行了修改,对调用方是可见的。
有一些语言支持使用某种方式来实现传引用调用,但是很少有语言是默认使用该方案的,比如C++等都是默认值传递。
而对于像C语言这种,它实际上是通过指针的方式,来模拟传引用调用的效果(因此也会被称为:传地址调用)。
传共享对象调用
此方式由 Barbara Liskov 命名,并被 Python、Java(对象类型)、JavaScript、Scheme、OCaml 等语言使用。
与传引用调用不同的一点在于:调用者传入变量到被调函数内的参数,如果有赋值操作,并不会影响调用者那边变量。
但是如果被调函数内部对传入的参数对象内部做了调整,那么会直接影响到调用方那边,举个例子:
public void test1() {
Person p1 = new Person("张三");
test2(p1); // 执行完test2之后,p1仍旧指向张三
test3(p1); // 执行完test3之后,p1内部name属性就变成了王五
}
public void test2(Person p) {
p = new Person("李四");
}
public void test3(Person pp) {
pp.setName("王五");
}
执行完test2之后的示意图:
image.png执行完test3之后的示意图
image.png比较在 Java 编程中传递参数的不同方式
在Java中根据数据类型的不同,可能表现有两种不同的穿参形式:
-
对于基础数据类型的传参,使用的是传值调用,也就是说在参数从调用方传入到被调函数时,会完整复制一份数据拷贝,被调函数内部的所有修改都是修改的拷贝后的副本
-
对于引用数据类型,它的表现更偏向于传共享对象调用,而非传的沸沸扬扬的传引用调用。
使用按值调用和按引用调用有什么好处?
按值调用
数据完全拷贝一份,这样不用担心被调函数内部对数据做修改,所有的内部修改,影响范围仅限于被调函数内部,出了方法后,调用方仍旧还是持有之前的数据内容。
按引用调用
这种的好处就是可以在被调函数内部影响调用者,对于有些应用场景是很有必要的,有时候有些数据比较复杂,需要单独的方法去做加工处理,处理后的参数可以直接反映到调用者那里,方便快速,同时也省去了复制拷贝所需的空间存储,因为这类对象一般都属于大对象,复制一份既费时也费空间,不划算。
结论
-
在Java中,既有传值调用,同时也有传共享对象调用。
-
所谓的传引用调用很少有语言会默认开启它,同时有些语言并非是直接支持传引用调用,而是会通过一些其他的方式来间接达到传引用调用的效果,比如:C语言的指针,有时候会被称为传地址调用
-
传值调用需要完整拷贝数据,开辟新的内存空间来存储;
-
传引用调用无需整个拷贝数据,调用方和被调函数共享同一个数据引用
-
传共享调用和传引用调用类似,不过不同的地方在于:被调函数内部如果对入参进行了赋值操作,则不影响调用方;只有被调函数内部对入参对象内部做了调整才会影响到调用方。
-
Java中基础数据类型参数在方法调用传入时,是使用传值调用的方案;而对于引用类型,则是传共享对象调用