所以同理,类似于这样的代码也会影响到外部原始数据:
@Test public void testList(){ List<Integer> list = new ArrayList<>(); list.add(1); addList(list); System.out.println(list); } private void addList(List<Integer> list) { list.add(2); } [1, 2]
那如果是这样的代码:
@Test public void test02(){ Car car1 = new Car("benz"); modifyCar(car1); System.out.println(String.format("最终结果 main car1==%s", car1)); } private void modifyCar(Car car2) { System.out.println(String.format("修改之前 car2==%s", car2)); car2 = new Car("bmw"); System.out.println(String.format("修改之后 car2==%s", car2)); }
假设 Java
是引用传递那最终的结果应该是打印 bmw
才对。
修改之前 car2==Car{name='benz'} 修改之后 car2==Car{name='bmw'} 最终结果 main car1==Car{name='benz'}
从结果又能佐证这里依然是值传递。
如果是引用传递,原本的 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 }
所以我们可以直接对数据进行修改,相当于间接的带了指针。