Dictionary 为什么切片值有时会过时,但从不映射值?

Dictionary 为什么切片值有时会过时,但从不映射值?,dictionary,pointers,go,reference,slice,Dictionary,Pointers,Go,Reference,Slice,我发现切片映射函数和通道经常作为引用类型一起提到。然而,我注意到有些东西表现出非引用行为,就像它们会过时一样: var s []int //must update slice value s = append(s, ...) 或 通常我是通过记住slice描述器实现的内部组件来理解这一点的:slice值可以被看作len、cap和数据指针的结构 但地图值永远不需要像这样麻烦 m := make(map[string]int) ... // don't kno

我发现切片映射函数和通道经常作为引用类型一起提到。然而,我注意到有些东西表现出非引用行为,就像它们会过时一样:

   var s []int
   //must update slice value
   s = append(s, ...) 

通常我是通过记住slice描述器实现的内部组件来理解这一点的:slice值可以被看作len、cap和数据指针的结构

但地图值永远不需要像这样麻烦

   m := make(map[string]int)
   ...
   // don't know how to express with insertion, but you know what i mean.
   m = delete(m, "well")  

为什么??映射值只是映射描述符的指针吗?如果是这样的话,为什么不这样做呢?

slice是连续内存块上的薄纸包装,通常可以部分或全部重用该内容(避免复制数据)。地图没有这些特征。它是一个复杂的数据结构,行为复杂,不能重用它的存储(就像你用C++那样)。在围棋中,一切都是按价值传递的。在Go中使用术语“reference type”时,它表示引用它们应该表示的数据的类型(通过指针)

切片是由以下类型表示的类似结构的小数据结构:

它包含一个指针,指向底层数组中切片的第一个元素(
SliceHeader.Data
field)。该结构很小,作为值传递很有效,无需传递其地址(并取消引用以间接访问其任何字段)。切片的元素不存储在切片标头中,而是存储在标头内存区域之外的数组中。这意味着修改“定点”元素将修改原始切片的元素

当您将(超过0)个元素附加到切片时,标头中的
Len
字段必须更改,因此使用附加元素描述切片的新切片必须与附加之前的切片不同,这就是为什么您需要指定内置
append()
函数的返回值。(其他值也可能会更改,但
Len
sure必须更改。)

映射被实现为指向
运行时.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
}
正如您所看到的,这是一个比切片头复杂得多的数据结构,而且要大得多,将其作为值传递是没有效率的

从映射中添加/删除元素(键值对)存储在由此结构的字段引用的存储桶中,但由于映射在后台作为指针处理,因此不需要分配此类操作的结果

完整地说,通道也被实现为指针,指向
运行时
包的
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
}
这也是一个“fat”结构,处理方式类似于映射值

参见相关问题:


对于初学者来说,将(非指针)切片传递到函数中最令人困惑的事情之一是:函数可以更改切片中的元素,但不能更改容量或长度。因此,如果切片具有容量,则追加可能会起作用,但如果没有,并且切片被重新分配到封盖下,则此更改将丢失,因为复制的切片标头发生了更改。解决方案:传递片地址。@colminator或返回新片,就像内置的
append()
一样,并让调用者分配它。这非常详细,谢谢@icza。顺便说一句,我注意到我的问题被否决了,这是否意味着我的思考或描述方式不是通常的gopher方式,并且可能受到其他编程语言的影响,这不是一个好的开始?
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
}
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
}