SANSUI'S BLOG

系统外观
分类标签
RSS
Sansui 2023
All rights reserved
人活着就是为了卡卡西

关于指针与Golang的结构体

11 月 27 日, 2020

之前在学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 just p.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
更新于 2020-11-27 08:00
Waline