当你试图分配一个字面的指针、地图值或一个函数返回值时,你的IDE或编译器会打印出错误。
"cannot take address of XXX"
其中XXX 是你要分配的元素。参见无效赋值的例子和编译器错误。
获取一个函数返回的值的地址
对于
t := &time.Now() // returns compiler error
你可以得到
cannot take the address of time.Now()
而对于
fooPtr := &foo() // returns compiler error
你得到
cannot take the address of foo()
获取地图值的地址
为
m := map[int64]int64{
1: 2,
}
a := &m[1] // returns compiler error
你会得到
cannot take the address of m[1]
获取非类型常数的地址
读取
i := &4 // returns compiler error
你会得到
cannot take the address of 4
获取类型常数的地址
读取
i := &int64(4) // returns compiler error
你会得到
cannot take the address of int64(4)
为什么你不能这样做
为了能够用& 操作符得到一个地址,即执行&x 操作,x 必须是可寻址的。正如《Go编程语言规范》所说。
对于类型为T的操作数x,寻址操作&x产生一个类型为*T的指针指向x。操作数必须是可寻址的,也就是说,要么是变量、指针指示或分片索引操作;要么是可寻址结构操作数的字段选择器;要么是可寻址数组的阵列索引操作。作为可寻址要求的一个例外,x也可以是一个(可能是括号内的)复合字面。如果对x的评估会引起运行时的恐慌,那么对&x的评估也会引起恐慌。
常量、地图索引表达式和由函数返回的值是不可寻址的,所以你不能对它们使用& 操作符。实际上,像4 、int64(4) 这样的常量或像foo() 这样的函数返回值没有地址,因为没有为它们分配特定的内存;它们可以驻留在处理器寄存器中。你可以类似地考虑地图的索引值。如果一个地图的元素数量增长超过一定的阈值,它就会被重新分配。如果有可能检索一个地图元素的地址,这样的指针在这次重新分配后可能指向一个无效的地址。由于这个原因,获取一个地图值的地址是不允许的。
作为一个经验法则,你可以把& 作为一个操作符,用于获取一些现有变量的地址,但有一个例外:你可以创建一个复合字面,并使用& 来检索其地址,例如,&T{},&map[string]int64{"a": 1} 或&[]int{} 是有效的表达式。
你可以做什么
在日常编码中,一个常见的模式,特别是在编写测试时,是将常量分配给一个结构指针。有了一个结构。
type A struct {
Val *int
}
你不能用看起来最简单的方式分配常量
a := A{
Val: &int(3), // compiler error: cannot take address of int(3)
}
然而,还有其他选择。让我们来分析一下它们。
使用辅助变量
最简单也是最值得推荐的将指针分配给一个值的方法是创建一个辅助变量,其地址可以被占用。
v := 3
a := A{
Val: &v,
}
使用一个辅助函数
如果你有多个地方想把指针分配给一个常量,你可以创建一个辅助函数,该函数接收一个具体的值并返回其地址。
func intPtr(i int) *int {
return &i
}
...
v := 3
a := A{
Val: intPtr(v),
}
使用new() 函数
如果你需要赋值一个给定类型的零值,你可以使用一个内置的 new()函数来创建一个新的变量并获取其地址。
a := A{
Val: new(int),
}
使用一个匿名函数
这等同于创建一个辅助函数,但这里我们创建的是一个匿名的单行函数。
a := A{
Val: func(i int) *int { return &i }(4),
}
使用一个片断
分片的索引操作是可寻址的,所以你可以用一个给定的值初始化一个新的分片,并获取其第一个元素的地址。然而,这种方法被认为是丑陋的,不推荐使用。
a := A{
Val: &[]int{3}[0],
}