在Go语言中只有一种参数传递的规则,那就是值拷贝,其包含两种含义:

  • 函数参数传递时使用的值拷贝
  • 实例赋值给接口变量,接口对实例的引用是值拷贝

我们在使用过程中会发现有时明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:

  • 直接传递的是指针。指针传递同样是值拷贝,但指针和指针副本的值指向的地址是同一个地方,所以能修改实参
  • 参数是复合数据类型,这些符合数据类型内部有指针类型的元素,此时参数的值拷贝并不影响指针的指向。

在Go语言中,复合类型chan、map、slice、interface内部都是通过指针指向具体的数据,这些类型的变量在作为函数参数传递时,实际上相当于指针的副本。我们可以通过查看源码,看一看他们的底层数据结构:

  1. map的底层数据结构:
//src/runtime/map.go1.14
// A header for a Go map.
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
}

通过源码我们可以分析,其通过buckets指针来间接引用map中的存储结构。
2. slice的底层数据结构:

//src/reflect/value.go1.14
// sliceHeader is a safe version of SliceHeader used within this package.
type sliceHeader struct {
   Data unsafe.Pointer
   Len  int
   Cap  int
}

slice则采用uinptr指针指向底层存放数据的数组。
3. interface的底层数据结构如下:

//src/reflect/value.go1.14
// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
   // see ../runtime/iface.go:/Itab
   itab *struct {
      ityp *rtype // static interface type
      typ  *rtype // dynamic concrete type
      hash uint32 // copy of typ.hash
      _    [4]byte
      fun  [100000]unsafe.Pointer // method table
   }
   word unsafe.Pointer
}
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}

我们可以看到接口内部通过一个指针指向实例值或地址的副本。
4. chan的底层数据结构如下:

//src/runtime/chan.go1.14
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
}

通过源码我们可以看出,通道元素的存放地址由buf指针确定,chan内部的数据也是间接通过指针访问的。