常见的不可寻址情况
- 常量的值。
- 基本类型值的字面量。
- 算术操作的结果值。
- 对各种字面量的索引表达式和切片表达式的结果值。不过有一个例外,对切片字面量的索引结果值却是可寻址的。
- 对字符串变量的索引表达式和切片表达式的结果值。
- 对字典变量的索引表达式的结果值。
- 函数字面量和方法字面量,以及对它们的调用表达式的结果值。
- 结构体字面量的字段值,也就是对结构体字面量的选择表达式的结果值。
- 类型转换表达式的结果值。
- 类型断言表达式的结果值。
- 接收表达式的结果值。
特点:不可变的、临时结果、不安全的
Map 取值
1
2
3
4
5
6
7
8
|
package main
import "fmt"
func main() {
m := map[int]bool{0: false}
fmt.Printf("%p", &m[0]) // invalid operation: cannot take address of m[0] (map index expression of type bool)
}
|
这是个比较常见的不可寻址的例子。原因也比较简单,map 类型是通过哈希表实现的,随着 map 元素的增多,可能触发扩容,那么 map 的值的位置发生改变,即其地址会发生变化,所以无法对 map 的值寻址。
另一方面,如果元素不存在于 map 中,返回零值,而零值是不可变对象,是不能寻址的(golang 中不可变对象是不可寻址的,如常量)。
存储在接口中的具体值
接口实现、方法集
存储在接口中的具体值是不可寻址的,就像 Map 元素是不可寻址的一样。因此,当你调用一个接口上的方法时,它必须有一个相同的接收者类型,或者它必须可以直接从具体的类型中分辨出来:指针型和值型接收方法可以分别用指针和值来调用。值接收方法可以用指针值来调用,那是因为它们可以先被解除引用。然而,指针接收方法不能用值来调用,因为存储在接口内的值没有地址。当给一个接口赋值时,编译器会确保所有可能的接口方法都能在这个值上被调用,因此试图进行不恰当的赋值会在编译时失败。为了扩展前面的例子,下面描述了什么是有效的,什么是无效的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
type List []int
func (l List) Len() int { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }
type Appender interface {
Append(int)
}
func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}
type Lener interface {
Len() int
}
func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}
func main() {
// A bare value
var lst List
CountInto(lst, 1, 10) // INVALID: List does not implement Appender (method Append has pointer receiver)
if LongEnough(lst) { // VALID: Identical receiver type
fmt.Printf(" - lst is long enough")
}
// A pointer value
plst := new(List)
CountInto(plst, 1, 10) // VALID: Identical receiver type
if LongEnough(plst) { // VALID: a *List can be dereferenced for the receiver
fmt.Printf(" - plst is long enough")
}
}
|
方法集决定了一个值实现了哪些接口。方法集简单概括: T 的方法集包括 T 接收者的方法; T 的方法集包括接收者T 和 T 的接收者. 在任何已经是指针或其地址可以被获取的东西上调用指针值方法是合法的。对任何是值或其值可以被取消引用的东西调用一个值方法是合法的。