go值传递
参数值传递
函数传递的总是原来这个东西的一个副本,一副拷贝。比如我们传递一个int类型的参数,传递的其实是这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个该指针的一份拷贝,而不是这个指针指向的值。
对于int这类基础类型我们可以很好的理解,它们就是一个拷贝,但是指针呢?我们觉得可以通过它修改原来的值,怎么会是一个拷贝呢?下面我们看个例子。
func main() {
i:=10
ip:=&i
fmt.Printf("原始指针的内存地址是:%p\n",&ip)
modify(ip)
fmt.Println("int值被修改了,新值为:",i)
}
func modify(ip *int){
fmt.Printf("函数里接收到的指针的内存地址是:%p\n",&ip)
*ip=1
}
运行结果: 原始指针的内存地址是:0xc42000c028 函数里接收到的指针的内存地址是:0xc42000c038 int值被修改了,新值为: 1
首先我们要知道,任何存放在内存里的东西都有自己的地址,指针也不例外,它虽然指向别的数据,但是也有存放该指针的内存。
所以通过输出我们可以看到,这是一个指针的拷贝,因为存放这两个指针的内存地址是不同的,虽然指针的值相同,但是是两个不同的指针。
参数引用传递
Go语言(Golang)是没有引用传递的。
引用类型
func main() {
persons:=make(map[string]int)
persons["张三"]=19
mp:=&persons
fmt.Printf("原始map的内存地址是:%p\n",mp)
modify(persons)
fmt.Println("map值被修改了,新值为:",persons)
}
func modify(p map[string]int){
fmt.Printf("函数里接收到map的内存地址是:%p\n",&p)
p["张三"]=20
}
输出: 原始map的内存地址是:0xc42000c028 函数里接收到map的内存地址是:0xc42000c038 map值被修改了,新值为: map[张三:20]
map作为函数的参数时,也是值传递,因为地址是不一样的。但是通过函数我们却能改变map里的值,为何?
通过查看src/runtime/hashmap.go源代码发现,make函数返回的是一个hmap类型的指针*hmap。也就是说map == *hmap。 现在看func modify(p map)这样的函数,其实就等于func modify(p *hmap)。
所以在这里,Go语言通过make函数,字面量的包装,为我们省去了指针的操作,让我们可以更容易的使用map。这里的map可以理解为引用类型,但是记住引用类型不是传引用。
channel类型与map类型同理。src/runtime/chan.go源码中可以看到make函数其实是返回的*hchan
slice和map、chan都不太一样的,一样的是,它也是引用类型,它也可以在函数中修改对应的内容。 但是它并不是对指针的换个面具。而是它字段里有指针类型。 我们来看slice大概的结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
所以修改类型的内容的办法有很多种,类型本身作为指针可以,类型里有指针类型的字段也可以。
单纯的从slice这个结构体看,我们可以通过modify修改存储元素的内容,但是永远修改不了len和cap,因为他们只是一个拷贝,如果要修改,那就要传递*slice作为参数才可以。
总结
最终我们可以确认的是Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan、func等这些),这样就可以修改原内容数据。
是否可以修改原内容数据,和传值、传引用没有必然的关系。 在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型。
这里也要记住,引用类型和传引用是两个概念,go函数只有值传递。