Go——值、指针和引用

传值还是传引用

在函数和接口章节,我们知道Go只有一种参数传递规则,那就是值拷贝,这种规则包括两种含义:

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

有时在明明是值拷贝的地方,结果却修改了变量的内容,有以下两种情况:

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

Go复合类型中chan、map、slice、interface内部都是通过指针指向具体的数据,这些类型的变量在作为函数参数传递时,实际上相当于指针的副本。

  • chan的底层数据结构如下:
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
}

//从chan在runtime里面的数据结构可知,通道元素的存放地址由buf指针确定,chan内部的数据也是间接通过指针访问的。
  • map的底层数据结构如下:
type hmap struct {
	// Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and
	// ../reflect/type.go. Don't change this structure without also changing that code!
	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
}

//从map在runtime里面的数据结构同样可以清楚地看到,其通过buckets指针来间接引用map中的存储结构。
  • slice的底层数据结构如下
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

//slice一样用uintptr指针指向底层存放数据的数组
  • inteface的底层数据结构如下
type nonEmptyInterface struct {
	// see ../runtime/iface.go:/Itab
	itab *struct {
		ityp   *rtype // static interface type
		typ    *rtype // dynamic concrete type
		link   unsafe.Pointer
		bad    int32
		unused int32
		fun    [100000]unsafe.Pointer // method table
	}
	word unsafe.Pointer
}

2、函数名的意义

Go的函数名和匿名函数字面量的值有3层含义:

  1. 类型信息,表明其数据类型是函数类型。
  2. 函数名代表函数的执行代码的起始位置。
  3. 可以通过函数名进行函数调用,函数调用格式为func name(param list)。在底层执行层面包含以下4部分内容。
    • 准备好参数。
    • 修改PC值,跳转到函数代码起始位置开始执行。
    • 复制值到函数的返回值栈区。
    • 通过RET返回到函数调用的下一条指令处继续执行。

3、引用语义

C++里面的引用的含义就是别名,Go语言规范中并没有引用的概念,但为了论述方便,闭包对外部变量的引用,我们可认为是建立了一个和外部变量同名的“引用”,该引用和外部变量指向相同的地址。还有一种解释就是Go语言针对闭包,显式地扩大了形参的可见域,使其在函数返回的闭包中仍然可见。这两种论述都没有错,本质上描述的是同一件事情,就是闭包可以访问和改变外部环境中的变量。至于是“同名引用”,还是“扩大作用域”,这些只是对闭包这个语言特性的规范表述。

package main

func main() {
	//f是一个闭包,包括对函数fa形式参数a的『同名引用
	f := fa(1)

	println(f(1)) //2
	println(f(2)) //4
}

func fa(a int) func(i int) int {
	return func(i int) int {
		println(&a, a)
		a = a + i
		return a
	}
}

//结果
0xc42001c058 1
2
0xc42001c058 2
4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值