一文搞懂参数传递原理 (2)

如果是引用传递,原本的 0x1102 应该是被直接替换为新创建的 0x1103 才对;而实际情况如上图所示,car2 直接重新引用了一个对象,两个对象之间互不干扰。

Go

相对于 Java 来说 Go 的用法又有所不同,不过我们也可以先得出结论:

Go语言的参数也是值传递。

在 Go 语言中数据类型主要有以下两种:

一文搞懂参数传递原理

值类型与引用类型;

值类型

先以值类型举例:

func main() { a :=10 modifyValue(a) fmt.Printf("最终 a=%v", a) } func modifyValue(a int) { a = 20 } 输出:最终 a=10

一文搞懂参数传递原理

函数调用过程与之前的 Java 类似,本质上传递到函数中的值也是 a 的拷贝,所以对其的修改不会影响到原始数据。

当我们把代码稍加修改:

func main() { a :=10 fmt.Printf("传递之前a的内存地址%p \n", &a) modifyValue(&a) fmt.Printf("最终 a=%v", a) } func modifyValue(a *int) { fmt.Printf("传递之后a的内存地址%p \n", &a) *a = 20 } 传递之前a的内存地址0xc0000b4040 传递之后a的内存地址0xc0000ae020 最终 a=20

从结果来看最终 a 的值是被方法修改了,这点便是 Go 与 Java 很大的不同点:

在 Go 中存在着指针的概念,我们可以将变量通过指针的方式传递到不同的方法中,在方法里便可通过这个指针访问甚至修改原始数据。

那这么一看不就是引用传递嘛?

其实不然,我们仔细看看刚才的输出会发现参数传递前后的内存地址并不相同。

传递之前a的内存地址0xc0000b4040 传递之后a的内存地址0xc0000ae020

这也恰好论证了值传递,因为这里实际传递的是指针的拷贝。

也就是说 modifyValue 方法中的参数与入参的&a都是同一块内存的指针,但指针本身也是需要内存来存放的,所以在方法调用过程中新建了一个指针 a ,从而导致他们的内存地址不同。

虽然内存地址不同,但指向的数据都是同一块,所以方法内修改后原始数据也受到了影响。

引用类型

对于 map slice channel 这类引用类型又略有不同:

func main() { var personList = []string{"张三","李四"} modifySlice(personList) fmt.Printf("slice=%v \n", personList) } func modifySlice(personList []string) { personList[1] = "王五" } slice=[张三 王五]

最终我们会发现原始数据也被修改了,但我们并没有传递指针;同样的特性也适用于 map 。

但其实我们查看 slice 的源码会发现存放数据的 array 就是指针类型:

type slice struct { array unsafe.Pointer len int cap int }

所以我们可以直接对数据进行修改,相当于间接的带了指针。

使用建议

那我们在什么时候使用指针呢?有以下几点建议:

如果参数是基本的值类型,比如 int,float 建议直接传值。

如果需要修改基本的值类型,那只能是指针;但考虑到代码可读性还是建议将修改后的值返回用于重新赋值。

数据量较大时建议使用指针,减少不必要的值拷贝。(具体多大可以自行判断)

Python

在 Python 中变量是否可变是影响参数传递的重要因素:

一文搞懂参数传递原理

如上图所示,bool int float 这些不可变类型在参数传递过程中是不能修改原始数据的。

if __name__ == '__main__': x = 1 modify(x) print('最终 x={}'.format(x)) def modify(val): val = 2 最终 x=1

原理与 Java Go中类似,是基于值传递的,这里就不再复述。

这里重点看看可变数据类型在参数传递中的过程:

if __name__ == '__main__': x = [1] modify(x) print('最终 x={}'.format(x)) def modify(val): val.append(2) 最终 x=[1, 2]

最终数据受到了影响,那么就表明这是引用传递嘛?再看个例子试试:

if __name__ == '__main__': x = [1] modify(x) print('最终 x={}'.format(x)) def modify(val): val = [1, 2, 3] 最终 x=[1]

显而易见这并不是引用传递,如果是引用传递最终 x 应当等于 [1, 2 ,3] 。

从结果来看这个传递过程非常类似 Go 中的指针传递,val 拿到的也是 x 这个参数内存地址的拷贝;他们都指向了同一块内存地址。

所以对这块数据的修改本质上改的是同一份数据,但一旦重新赋值就会创建一块新的内存从而不会影响到原始数据。

image.png

与 Java 中的上图类似。

所以总结下:

对于不可变数据:在参数传递时传递的是值,对参数的修改不会影响到原有数据。

对于可变数据:传递的是内存地址的拷贝,对参数的操作会影响到原始数据。

这么说来这三种都是值传递了,那有没有引用传递的语言呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygggd.html