值传递
Golang中参数的传递全部为值传递,即传递的参数变量的一个副本,一副拷贝。
比如我们传递一个int类型的参数,传递的其实是这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个该指针的一份拷贝,而不是这个指针指向的值。
示例
package main
import (
"fmt"
)
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
}
输出:
原始指针的内存地址是:0xc0000a0018
函数里接收到的指针的内存地址是:0xc0000a0028
int值被修改了,新值为: 1
引用类型
Golang的引用类型包括Slice、Map和Channel。除了指针类型以外,当参数为Slice、Map、Channel时,函数内的修改仍然会影响函数外的值。
示例
package main
import (
"fmt"
)
func main() {
s := []int{1,2,3}
modify(s)
fmt.Println(s)
}
func modify(s []int){
s[0] = 0
}
输出:
[0 2 3]
Program exited.
原理
Slice
在golang中,Slice 的数据结构定义如下:
type slice struct {
array unsafe.Pointer
len int
cap int
}
传递slice的时候,这个结构体是值传递的,传递完成后,内存中有两个slice结构体,它们引用同一块slice数组。
示例
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
s := []string{"a", "b"}
fmt.Printf("%p\n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
sliceData(s)
}
func sliceData(s []string) {
fmt.Printf("%p\n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
}
输出:
0xc00000c030
0xc00000c048
SliceHeader
SliceHeader是Slice运行时的具体表现,Slice在运行中可以通过unsafe.Pointer进行强制类型转换,转换为reflect.SliceHeader,SliceHeader的结构定义如下:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
Map
对于map,make函数返回的是一个hmap类型的指针*hmap。因此在传递时,传递的是这个指针的复制。
// makemap implements a Go map creation make(map[k]v, hint)
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If bucket != nil, bucket can be used as the first bucket.
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
//省略无关代码
}
Chan
与map同理。
func makechan(t *chantype, size int64) *hchan {
//省略无关代码
}
使用
扩容
如果因为容量大小触发了Slice的扩容,函数内外的引用就不一样了。
package main
import (
"fmt"
)
func main() {
s := []int{1,2,3}
modify(s)
fmt.Println(s)
}
func modify(s []int){
s = append(s, 4)
s[0] = 0
fmt.Println(s)
}
输出:
[0 2 3 4]
[1 2 3]
如何在扩容情况下修改原切片
除了返回新切片外,还可以传递切片的指针
func myAppend(list *[]string, value string) {
*list = append(*list, value)
}
利用slice传递数组
数组作为参数的时候,会将其复制一份,如果它非常大,「会造成大量的内存浪费」,利用[:]将数组作为切片传递。
package main
import (
"fmt"
)
func main() {
a := [3]int{1,2,3}
print(a[:])
}
func print(a []int){
fmt.Println(a)
}