这里不会解释关于指针的情况,如果读者对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里面所有的传参都是传值。
支持传引用的几个数据结构同时通过指针来维护同一个变量,从而实现传引用的,但是数据结构本身也是会被拷贝的。
在这儿就需要对内存的空间,指针和变量的关系有一定理解才能更好地理解这个问题。
如果不能理解,那就死记硬背上面说的哪些变量是传引用哪些是传值。