先说结论
可以直接取地址,就是可以在expression侧直接用&操作符,其范围仅仅为:
1、变量都可取地址
2、字面量--复合类型初始化。
3、表达式---迂回指针和变量嵌套。
一个原则
可取地址,就代表该值在内存中可见,可寻找。
以赋值操作为例,变量名v = 变量值expression,整体作为一个变量。expression初始化了一个值,然后赋值给变量名v(变量名就是一个内存地址),这个值才拥有一个地址。才可见,可寻找。
var v type = expression
变量名v其实就是内存地址的一个助记符,代表整个变量。(类型type用于编译器确定分配的内存大小空间,expression提供一个该类型的值。)
大原则:只要是变量都是可以取地址的。
字面量例外
1、字面量是不可取地址的,但是作为例外,复合类型字面量的初始化,是可以取地址的,相当于初始化了复合类型和该值的指针。这是一个例外。
[复合类型]: arrays, slices ,struct,map
x := &People{"name"}
常见的字面量都是常量。常量没有地址,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。它和内存没有映射关系。
[引用类型]:pointer,function,channel ,slice ,map 。引用类型都是有地址的,但是其字面量却只有slice和map可取地址,pointer、function、channel都无法取地址,可能是出于安全考虑。
注意,这个例外只是在字面量的前提下,make和new的复合类型都是不能取地址的。
表达式例外
1、间接变量
pointer indirection 迂回指针(取值操作)
理解:间接变量,取值操作,就是取到了变量,是可以进行取地址操作,符合大原则。
x := 2
xx := &x
y := &*xx
2、变量的嵌套key,也是变量
如果有下层嵌套的key,主要看上层是不是变量(或间接变量)
2.1、slice indexing operation
理解:切片因为是引用类型的,天生初始化的时候就有地址的(可以视为变量),索引操作其实就是进行了偏移,能得到地址。
x := []int{1, 2, 3}
xx := &x[2]
同样的,如果切片没有赋值给变量x,那么切片本身是个引用类型本身初始化的时候是有地址的。因此也是可以获取地址的。
x := &([]int{1, 2, 3}[2])
2.2、an array indexing operation of an addressable array
理解:道理和slice一样,只不过slice是引用类型,初始化的时候会自带地址。而array作为基本值类型,初始化的时候不会初始化地址。
数组赋值给变量x,拥有地址(可视为变量),所以其索引操作也可以取地址。
x := [3]int{1,2,3}
xx := &x[0]
而如果数组没有地址的话,那么其索引操作便无法取地址。
x := &[3]int{1,2,3}[0] //error,这点和slice不同,因为key的父节点不是变量
2.3、a field selector of an addressable struct operand
理解:struct有地址,那么字段就有地址。编译器会计算每个字段的偏移,和slice有点类似,能计算每个字段的地址。
People作为变量名p,因此是可寻址的,所以其字段也可以寻址。
p := People{"p"}
pp := &p.Name
相反的,如果struct初始化的时候没有分配地址(不可视为变量),那么其字段就无法取地址
p := &(People{"p"}.Name) //error
如果给struct初始化的时候,也初始化其地址,就可以取地址了。
p := &((&People{"p"}).Name)
注意1: expression如果是map是嵌套key不能取地址
理解:map在rehash的时候,是隐式的,不像slice会把重新分配的地址显示返回。所以为了安全,不提供key的取地址操作。这个嵌套的key也是个例外。
m := &map[string]int{"name":1}["name"] //error
注意2: expression如果是方法调用不能取地址
理解:如果了解方法调用时的内存分配,返回值的值在调用栈,返回值的变量在被调用栈中。因此返回值在调用者的作用域内,地址不可见。
总结
可以取地址的仅仅为:
1、变量都可取地址
2、字面量--复合类型初始化。
3、表达式---迂回指针和变量嵌套。
为什么要取地址
只有可取地址的值,才能被赋值。