golang传值和传引用

这里不会解释关于指针的情况,如果读者对C语言或者C++的指针比较了解,那么就能更好地理解本文。

定义

对于代码

modify(a);
a.modify();

如果modify中对于a的修改不会改变传入的a的值,那么就是传值调用;否则,是传引用。

传值调用是将传入的变量在内存中复制一份进行操作,所以本质是存储在不同内存地址的不同变量。

传引用是将传入变量的内存地址,在函数操作中,通过内存地址将变量取出进行操作,所以本质是存储在同一个内存地址的相同变量。

Go语言

基础类型

基本类型:byte,int,bool,string

均是传值调用。

数组

传值调用

func main() {
	var array = [3]int{0, 1, 2}
	var array2 = array
	slice2[2] = 5
	fmt.Println(array, array2)
}

// 输出结果:
// [0 1 2] [0 1 5]

使用指针来实现传引用:

func main() {
	var array = [3]int{0, 1, 2}
	var array2 = &array
	array2[2] = 5
	fmt.Println(array, *array2)
}
// 输出结果:
// [0 1 5] [0 1 5]

数组切片

传引用

理解数据结构的本质就能理解这个问题

数组切片的本质是一个如下的数据结构

在这里插入图片描述

包含一个pointer,一个长度,一个容积的结构。其中pointer指向的是作为主要存储空间的array。

在这里插入图片描述

那么在进行传入函数和赋值的时候,会将slice的结构赋值一份,但是pointer还是指向原地址。从而实现了传引用。

注:注意slice扩容和不扩容的情况。可见博文详解。

结构体

在函数中参数的传递可以是传值(对象的复制,需要开辟新的空间来存储该新对象)和传引用(指针的复制,和原来的指针指向同一个对象),建议使用指针,原因有两个:能够改变参数的值,避免大对象的复制操作节省内存。

Map

传引用

运行以下代码:

func main() {
	m := map[string]int{"value": 0}
	m1 := m
	fmt.Println("m =", m)
	fmt.Println("m1 =", m1)
	m1["value"] = 1
	fmt.Println("m =", m)
	fmt.Println("m1 =", m1)
}

/*
输出结果:
m = map[value:0]
m1 = map[value:0]
m = map[value:1]
m1 = map[value:1]
*/

map内部维护着一个指针,该指针指向真正的map存储空间。我们可以将map描述为如下结构:

type map[key]value struct{
	impl *Map_K_V
}

type Map_K_V struct{
	//......
}

channel

同slice和map。

function

传引用

传的是函数指针

注:在go中,这种指针其实长相和普通的值没有区别。就像array的指针也能直接用array[0]一样。

本质

如果对C和C++指针理解比较深的同学会发现,go里面所有的传参都是传值。

支持传引用的几个数据结构同时通过指针来维护同一个变量,从而实现传引用的,但是数据结构本身也是会被拷贝的。

在这儿就需要对内存的空间,指针和变量的关系有一定理解才能更好地理解这个问题。

如果不能理解,那就死记硬背上面说的哪些变量是传引用哪些是传值。