为什么切片值有时会过时但不能映射值?

问题描述:

我发现切片图功能和通道经常作为引用类型一起提及。但是,我注意到,某些切片没有表现出过时的参考行为:

I find that slice map function and channel are frequently mentioned together as reference types. However I notice that slices somethings exhibit none reference behavior like they can go stale:

   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)

通常,我会通过记住slice描述符实现的内部组件来理解这一点:slice值可以看作len,cap和data指针的结构。

Usually I understand this by keeping the inner components of the slice discriptor implementation in mind: A slice value can be seen as a struct of len, cap and data pointer.

但是地图值永远不需要麻烦

But map values need never bother like

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

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

Why? Is a map value just a pointer to the map descriptor? If so why not also make slice this way?

在Go中,没有像C ++中那样的引用类型。在Go中,一切都是通过价值传递的。在Go中使用引用类型一词时,它表示引用它们应表示的数据(通过指针)的类型。

In Go there are no reference types like you have them in C++. In Go everything is passed by value. When the term "reference type" is used in Go, it means a type that references to the data they ought to represent (via pointers).

切片很小,结构类型的数据结构,由类型 reflect.SliceHeader 表示>

Slices are small, struct-like data structures represented by the type reflect.SliceHeader:

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}

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

It contains a pointer to the first element of the slice in an underlying array (SliceHeader.Data field). This struct is small and is efficient to pass as a value, no need to pass its address (and dereference it to access indirectly any of its fields). The elements of a slice are not stored in the slice header, but in an array outside of the header's memory area. This means modifying a "pointed" element will modify the element of the original slice.

当您将(大于0)元素附加到切片时, Len 字段必须更改,因此描述带有附加元素的切片的新切片必须不同于追加之前的切片,这就是为什么您需要分配的返回值的原因内置的 append()函数。 (其他值也可能会更改,但是 Len 当然必须更改。)

When you append (more than 0) elements to a slice, the Len field in the header must change, so the new slice that describes the slice with the additional elements must be a different than the one before the append, that's why you need to assign the return value of the builtin append() function. (Other values may also change, but Len sure must change.)

映射被实现为指向 runtime.hmap 结构:

Maps are implemented as pointers to the runtime.hmap structure:

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的字段所引用的存储桶中,但是由于映射在后台作为指针处理,因此您无需分配此类操作的结果。

Adding / removing elements (key-value pairs) from a map is stored in buckets that are referenced by the fields of this struct, but since maps are handled as pointers under the hood, you do not need to assign the result of such operations.

为了完整起见,通道也被实现为指针,指向运行时包的 hchan 类型:

To be complete, channels are also implemented as pointers, pointing to the runtime package's hchan type:

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
}

这又是一个胖结构,

查看相关问题:

要在参数中使用的切片vs映射

使用值接收器将其附加到具有足够容量的切片上

golang切片是否按值传递?

值语义的含义是什么?和指针语义在Go中意味着什么?