传值还是传引用
在函数和接口章节,我们知道Go只有一种参数传递规则,那就是值拷贝,这种规则包括两种含义:
- 函数参数传递时使用的是值拷贝。
- 实例赋值给接口变量,接口对实例的引用是值拷贝。
有时在明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:
- 直接传递的是指针。指针传递同样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,所以能修改实参值。
- 参数是复合数据类型,这些复合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。
Go复合类型中chan、map、slice、interface内部都是通过指针指向具体的数据,这些类型的变量在作为函数参数传递时,实际上相当于指针的副本。
- chan的底层数据结构如下:
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
}
//从chan在runtime里面的数据结构可知,通道元素的存放地址由buf指针确定,chan内部的数据也是间接通过指针访问的。
- map的底层数据结构如下:
type hmap struct {
// Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and
// ../reflect/type.go. Don't change this structure without also changing that code!
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
}
//从map在runtime里面的数据结构同样可以清楚地看到,其通过buckets指针来间接引用map中的存储结构。
- slice的底层数据结构如下
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
//slice一样用uintptr指针指向底层存放数据的数组
- inteface的底层数据结构如下
type nonEmptyInterface struct {
// see ../runtime/iface.go:/Itab
itab *struct {
ityp *rtype // static interface type
typ *rtype // dynamic concrete type
link unsafe.Pointer
bad int32
unused int32
fun [100000]unsafe.Pointer // method table
}
word unsafe.Pointer
}
2、函数名的意义
Go的函数名和匿名函数字面量的值有3层含义:
- 类型信息,表明其数据类型是函数类型。
- 函数名代表函数的执行代码的起始位置。
- 可以通过函数名进行函数调用,函数调用格式为func name(param list)。在底层执行层面包含以下4部分内容。
- 准备好参数。
- 修改PC值,跳转到函数代码起始位置开始执行。
- 复制值到函数的返回值栈区。
- 通过RET返回到函数调用的下一条指令处继续执行。
3、引用语义
C++里面的引用的含义就是别名,Go语言规范中并没有引用的概念,但为了论述方便,闭包对外部变量的引用,我们可认为是建立了一个和外部变量同名的“引用”,该引用和外部变量指向相同的地址。还有一种解释就是Go语言针对闭包,显式地扩大了形参的可见域,使其在函数返回的闭包中仍然可见。这两种论述都没有错,本质上描述的是同一件事情,就是闭包可以访问和改变外部环境中的变量。至于是“同名引用”,还是“扩大作用域”,这些只是对闭包这个语言特性的规范表述。
package main
func main() {
//f是一个闭包,包括对函数fa形式参数a的『同名引用
f := fa(1)
println(f(1)) //2
println(f(2)) //4
}
func fa(a int) func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
//结果
0xc42001c058 1
2
0xc42001c058 2
4