Go 实践| nil 接收者的本质

35 阅读2分钟

一道面试题

首先看下面的面试题,你觉得输出结果是什么?

package main

import "fmt"

type person struct {
	name string
}

func (p *person) sayHi() {
	fmt.Println("hi,gopher")
}

func main() {
	var p *person
	p.sayHi()
}

像接触过 Java 或C# 等面向对象的语言,都会知道像这种支持定义了 P ,没有进行实例化的时候基本会报类似 null pointer exception。所以一开始我的答案也是执行 panic 了,输出以下的结果情况:

panic: runtime error: invalid memory address or nil pointer dereference

但是直接在编译器运行项目时,输出结果:

输出结果的时候有些疑惑创建 var p *person变量的时候GO编译器直接初始化该结构了吗?可以通过 print 函数输出 p 的结果看看:

func (p *person) sayHi() {
	fmt.Printf("接受者:%v \n", p)
	fmt.Println("hi,gopher")
}

执行输出结果:

可以看到输出的结果是 nil ,没有初始化该结构体。在面向对象语言中有点类似静态方法调用,但是接收者其实是一个参数。所以在我们没有初始化结构体的时候调用了方法,方法内部 nil 就会当作接受者进行传递。

所以以后遇到这样的面试题,可以大胆的回答输出的结果是hi gopher,并不会产生编译错误。

如果面试官继续追问,怎么会引起 panic ?答案也很简单,由于 nil 接收器只是一个参数,只有当该方法访问结构体字段的时候就会引起panic,比如Person 中的name。

func (p *person) sayHi() {
	fmt.Println(p.name)  // 这样编译时就会引起panic
	fmt.Println("hi,gopher")
}

方法内初始化

调用时没有初始化结构体,那如果在方法内部进行初始化接收器是否有影响?

func (p *person) sayHi() {
	if p == nil {
		p = new(person)
		p.name = "Tom"
	}
	fmt.Printf("hi, %s \n", p.name)
}

func main() {
	var p *person
	p.sayHi()
	fmt.Printf("外部:%v \n", p)
}

输出的结果可以看出没什么问题,内部初始化后对外部没有影响,变量 p 还是保持 nil ,如果在main 函数体中访问结构体字段就会发生 panic。

最后

整个题目的考点其实就是对 GO的方法的了解。在go 中函数和方法时不同的概念。函数是指不属于任何结构体、类型的方法,但是方法是有一个归属的,属于某结构体或其他新定义的类型。在定义方法时会在 func 和方法名之间增加一个参数,这个参数就是方法的接收者。所以:

  • 方法只是一个包含接收者参数的函数
  • 接收者就是一个参数