我发现切片图函数和通道经常作为引用类型一起提及。但是,我注意到,切片某些东西没有参考行为,就像它们会过时一样:
var s []int
//must update slice value
s = append(s, ...)
或
//must use pointer if we want to expose the change
func foo(s *[]int) error
//or change the function signature to return it like _append_
func foo(s []int) (r slice, err error)
通常,我会牢记切片描述符实现的内部组件来理解这一点:切片值可以看作是len,cap和data指针的结构。
但是地图值永远不需要麻烦
m := make(map[string]int)
...
// don't know how to express with insertion, but you know what i mean.
m = delete(m, "well")
为什么?映射值仅仅是指向映射描述符的指针吗?如果是这样,为什么还不这样做呢?
答案 0 :(得分:7)
在Go中,没有像C ++中那样的引用类型。在Go中,一切都是通过价值传递的。在Go中使用“引用类型”一词时,它表示引用它们应表示的数据(通过指针)的类型。
切片是小型的,类似于结构的数据结构,由类型reflect.SliceHeader
表示:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
它包含一个指向基础数组(SliceHeader.Data
字段)中切片的第一个元素的指针。该结构很小,可以有效地作为值传递,而无需传递其地址(并取消引用以间接访问其任何字段)。切片的元素不存储在切片头中,而是存储在头的存储区域之外的数组中。这意味着修改“指向”元素将修改原始切片的元素。
将一个(大于0个)元素追加到一个切片时,标头中的Len
字段必须更改,因此描述带有其他元素的切片的新切片必须与之前的切片不同。追加,这就是为什么您需要分配内置append()
函数的返回值的原因。 (其他值也可能会更改,但Len
当然必须更改。)
映射被实现为指向runtime.hmap
结构的指针:
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
}
如您所见,这是一个比切片头更复杂的数据结构,并且更大,将其作为值传递将是无效的。
在映射中添加/删除元素(键-值对)存储在此struct的字段所引用的存储桶中,但是由于映射在后台作为指针处理,因此您无需分配的结果这样的操作。
为完整起见,通道也被实现为指针,指向runtime
包的hchan
类型:
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
}
这又是一个“胖”结构,其处理方式类似于地图值。
查看相关问题:
slice vs map to be used in parameter
Appending to a slice with enough capacity using value receiver
Are golang slices pass by value?
What do "value semantics’" and "pointer semantics" mean in Go?
答案 1 :(得分:4)
Slice是连续内存块上的薄纸包装,通常有益的是部分或全部重用该内容(避免复制数据)。地图没有这些特征。这是一个具有复杂行为的复杂数据结构,您无法重复使用其存储(就像处理切片一样)。