dongsuo9982 2019-04-04 16:12
浏览 116
已采纳

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

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, ...) 

or

   //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)

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?

  • 写回答

2条回答 默认 最新

  • douyao3895 2019-04-04 16:43
    关注

    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).

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

    type SliceHeader struct {
            Data uintptr
            Len  int
            Cap  int
    }
    

    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.

    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.)

    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
    }
    

    As you can see, this is a lot more complex data structure that a slice header, and is a lot bigger, passing this as value would not be efficient.

    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.

    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
    }
    

    This is again a "fat" struct and is handled like map values.

    See related questions:

    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条)

报告相同问题?

悬赏问题

  • ¥20 ML307A在使用AT命令连接EMQX平台的MQTT时被拒绝
  • ¥20 腾讯企业邮箱邮件可以恢复么
  • ¥15 有人知道怎么将自己的迁移策略布到edgecloudsim上使用吗?
  • ¥15 错误 LNK2001 无法解析的外部符号
  • ¥50 安装pyaudiokits失败
  • ¥15 计组这些题应该咋做呀
  • ¥60 更换迈创SOL6M4AE卡的时候,驱动要重新装才能使用,怎么解决?
  • ¥15 让node服务器有自动加载文件的功能
  • ¥15 jmeter脚本回放有的是对的有的是错的
  • ¥15 r语言蛋白组学相关问题