关于指针与Golang的结构体
之前在学C++的时候,指针的用法就困惑了很久。后来有了计算机体系结构基础后,再加上Golang的一些说明,就突然明白了,之前为什么不理解指针。纠结的点在哪里。
指针的概念
(学的语言太多语法有些混了,看个意思就好,代码是四不像并不可以运行)
- 地址:一个变量在内存中的存储形式是 地址 + value,地址是在指内存中的地址。
比如 int a = 3
,意思是变量 a 在内存中有一个地址,这个地址储存的 value 为 3。
- 指针 (pointer) :是一种变量,它的 value 仍然是一个地址。常用
*
定义指针变量。
比如 int *b = &a
,意思是 指针变量 b 在内存中有一个地址,这个地址储存的 value 为 a 的地址。
在使用变量(而不是声明或定义变量)时,&
为取地址符。对应的还有一个*
为取内容符。比如
int a = 3
int *b = &a // 将 a 的地址作为 b 的 value
print(b) // 输出 b 的 value,结果为一个地址,等于 a 的地址(&a)
print(*b) //将 b 的 value 作为地址,输出地址中存的值,结果为 3
上面仅是个人的概念解释,尽量少地引入新概念。以上概念有更通俗的叫法。
比如int *b = &a
,通常会叫做“变量 b 持有 a 的引用”。个人觉得虽然直观,但对于初学者并不友好。首先,“变量 b”就没有说清楚指针变量的特殊性。然后“a 的引用”,倒是说清楚什么叫做 a 的引用啊……尤其是一些语言没有指针的概念,但引用是随处可见的(比如 Javascript 的 Object 类型)
Golang Struct 与 指针的访问
其实让我明白的只是因为Go tour中的两句话:
Struct fields can be accessed through a struct pointer. 结构体字段可以使用结构体指针获取。
To access the field X of a struct when we have the struct pointer p we could write
(*p).X
. However, that notation is cumbersome, so the language permits us instead to write justp.X
, without the explicit dereference. 结构体指针访问字段本来应该写成(*p).x
,但是由于这么写太蠢了,所以允许直接写成p.x
。
也就是说,如果见到类似T.x
的结构体访问,T有可能是结构体本身,也可能是指针……需要自行区分。仅此而已。
而我之前一直以为 T 只能是结构体本身= =,所以对于指针一直头大……
但由于存在指针这种特殊的访问方式,在结构体的组合与接口实现中会有一些想不到的情况:
-
定义了一个接口Interface,方法有Intera(),Interb()
type Interface interface{ Intera() Interb() }
-
定义了一个结构体Base,用结构体指针的方式实现了Intera()
type Base struct {} func (b *Base) Intera() {}
-
定义了一个结构体Extend,匿名组合了Base,用正常结构体的方式实现了Interb()
type Extend struct { Base } func (e Extend) Interb() {}
这个时候,请问有谁实现了Interface?
答案是:Base 和 Extend 本身都没有实现 Interface。但是上述代码中完全没有出现的 *Extend(Extend的指针)实现了Interface。
为什么呢?虽然 、*Extend 并没有实现第二个方法,但 Extend 实现了,所以 ***Extend **是也是可以直接访问第二个方法的(参考上面的(*p).x的解释)。
而*Base实现了第一个方法(Base没有实现),而 Extend 组合了 Base。因此第一个方法可以也通过 *Extend 访问(Extend无法访问)。
所以*Extend两个方法都能访问,因此实现了Interface。而 **Extend **只能访问第二个方法,因此没有实现Interface。
然后日常使用 Extend 的时候,为了能使用Interface的方法,需要使用 *Extend:
interfacelist := make([]Interface, 0)
interfacelist = append(interfacelist, &Extend{}) // 因为是指针实现Interface,需要传入地址
e = interfacelist[0]
在 goland 对 e 按下 F1 时,只会显示,这是个 Interface,不会告诉你这是 *Extend。如果不是自己从头写的代码,你可能很久都无法发现,是个指针类型实现了 Interface。你必须在层层组合中,找到是哪一层(这里是Base)让 Extend 变成了 Interface 的指针实现。
对 e 的 type assertion 也应该这么写:
e_ptr = e.(*Extend) // 从Interface类型返回一个Extend类型的指针
e_ptr.Base // 等于(*e_ptr).Base