在Go语言中只有一种参数传递的规则,那就是值拷贝,其包含两种含义:
- 函数参数传递时使用的值拷贝
- 实例赋值给接口变量,接口对实例的引用是值拷贝
我们在使用过程中会发现有时明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:
- 直接传递的是指针。指针传递同样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,所以能修改实参
- 参数是复合数据类型,这些符合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。
在Go语言中,复合类型chan、map、slice、interface内部都是通过指针指向具体的数据,这些类型的变量在作为函数参数传递时,实际上相当于指针的副本。我们可以通过查看源码,看一看他们的底层数据结构:
- map的底层数据结构:
//src/runtime/map.go1.14
// A header for a Go map.
type hmap struct {
// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
// Make sure this stays in sync with the compiler's definition.
count int // # live cells == size of map. Must be first (used by len() builtin)
flags uint8
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
hash0 uint32 // hash seed
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // optional fields
}
通过源码我们可以分析,其通过buckets指针来间接引用map中的存储结构。
2. slice的底层数据结构:
//src/reflect/value.go1.14
// sliceHeader is a safe version of SliceHeader used within this package.
type sliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}
slice则采用uinptr指针指向底层存放数据的数组。
3. interface的底层数据结构如下:
//src/reflect/value.go1.14
// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
// see ../runtime/iface.go:/Itab
itab *struct {
ityp *rtype // static interface type
typ *rtype // dynamic concrete type
hash uint32 // copy of typ.hash
_ [4]byte
fun [100000]unsafe.Pointer // method table
}
word unsafe.Pointer
}
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
我们可以看到接口内部通过一个指针指向实例值或地址的副本。
4. chan的底层数据结构如下:
//src/runtime/chan.go1.14
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
通过源码我们可以看出,通道元素的存放地址由buf指针确定,chan内部的数据也是间接通过指针访问的。