Golang Go语言中有关 nil 的一个疑问?

发布于 1周前 作者 itying888 来自 Go语言

今天突然意识到一个问题:go 的代码中,关于调用结构体指针绑定的方法的时候,方法里面似乎都没有判断该指针是否为 nil 的情况。

type A struct {
	a int
}

func (a *A) Run() { fmt.Print(a.a) }

func TestA(t *testing.T) { var a *A a.Run() }

以上代码会报错,原因也很明显,a 为 nil, a.Run() 方法中,访问 a.a 报错。 看了一下自己之前写的所有 go 代码,所有 结构体指针绑定的方法里面,都没有判断是否为 nil 的情况。


Golang Go语言中有关 nil 的一个疑问?

更多关于Golang Go语言中有关 nil 的一个疑问?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

37 回复

会 go 不会 go 的都哑口无言了

更多关于Golang Go语言中有关 nil 的一个疑问?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在一些项目源码里见到过判断的,不过我是觉得没必要。

这种一般是调用者保证它非 nil

这取决于哪里需要关心 a 为 nil 这件事

golang<br>type A struct {<br> a int<br>}<br><br>func (a *A) Run() {<br> fmt.Print(a.a)<br>}<br><br>func NewA()*A{<br> return &amp;A{}<br>}<br><br>func TestA(t *testing.T) {<br> //var a *A // 这一步只是声明了一个变量 a 为 结构体 A 指针类型,没有实例化<br> //<a target="_blank" href="http://a.Run" rel="nofollow noopener">a.Run</a>()<br> a := NewA() // 一般都是这样去实例化 <br> <a target="_blank" href="http://a.Run" rel="nofollow noopener">a.Run</a>()<br>}<br>

你这代码只是声明了 变量 a 是 A 类型。没有实例化

判不判断 nil 看需求,有时候结构体绑定的方法是当函数来用的
golang<br><br>import "fmt"<br><br>type A struct {<br> a int<br>}<br><br>func (a *A) Run() {<br> fmt.Print(a.a)<br>}<br><br>func main() {<br> var fn = (*A).Run<br> fmt.Println(fn)<br> fn(&amp;A{111})<br>}<br><br>

变量没初始化不能怪函数绑定。

此时,a==nil
所以,a.Run() == nil.Run()
作为一个 nil,怎么可能执行的了(a *A) Run() 这个函数呢?
这和 go 没有任何关系

你只是声明了个 A 指针类型变量,肯定会报错啊。

家人们 见鬼了
声明变量但不初始化对象 居然不能调用对象方法

func TestA(t *testing.T) {
var a = new(A)
a.Run()
}

试试呢😜

要么 new, 要么 &A{} 吧

想到了开心的事

golang<br>type A struct{}<br><br>func (*A) hello() {<br> fmt.Println("hello, world")<br>}<br><br>func main() {<br> var a *A<br> fmt.Printf("a is nil? %v\n", a == nil)<br> a.hello()<br>}<br>

是可以执行的,a 是一个有类型的 nil ,可以调用这个类型的指针方法

我要调某个结构体的方法,但是我不初始化这个结构体,哎,我就是玩

没毛病啊,你的 hello 又没用 A 里的东西,当然能跑。改成这样比较有意思:

type A struct{}

func (*A) hello() {
fmt.Println(“hello, world”)
}

func main() {
var a *A
var b interface{}
b = a
fmt.Printf(“a is nil? %v\n”, a == nil)
fmt.Printf(“b is nil? %v\n”, b == nil)
a.hello()
}

居然真的可以,涨知识了

Go 实际上编译生成的函数只是在语义层面上跟原始类型绑定,而在之后的代码生成过程中,Go 隐式生成了一个对应的函数,这个新的函数跟老的类型是解耦的,并把对象本身作为第一个参数传入。

举个例子来说,考虑你的测试代码,Go 编译器会生成下面的 stub:

go<br>func (*A).Run(a *A) {<br> fmt.Print(a.a)<br>}<br>

这种函数签名是你不能写出来的,因为在用户层面这种语法是 invalid 的,但是你可以调用。尝试在你的 Test 中插入:

go<br>func TestA(t *testing.T) {<br> var a *A<br> <a target="_blank" href="http://a.Run" rel="nofollow noopener">a.Run</a>()<br>}<br><br>func TestAnother(t *testing.T) {<br> var a *A<br> (*A).Run(a)<br>}<br>

代码可以正常编译执行,然后报错。

而假如我定义另一个方法,不访问 A 的内部参数,即不依赖 A 的值,你会发现代码能够安全运行,没有任何问题( 16 楼老哥已经有 runnable 了)。

这种情况是不是比较反直觉?而实际上 GCC 和 clang 也是这么做的: https://godbolt.org/z/xb9WWz53x 。当然,如果你打开 undefined behavior santizer ,你会发现编译器报错了,因为标准并没有对这个情况作出规定。

所以是否需要检查 receiver 是不是 nil ?取决于你。如果这个方法你不想 panic ,那你可以检查 if p != nil 并做出恰当报错,但是这会影响大家对于 method call 语义的共识理解(如果 a 为 nil ,我调用它的方法应该 panic 啊?为什么没有 panic ?)。或者直接 let it crash ,也方便查错。

go<br> // 以下是定义在结构体值上的方法原型,通过调用结构体类型上定义的函数,传入结构体的值<br> A.echo_A(a) // (_ A)<br> A.echoA(a, "a") // (A) a<br> // A.echo_жA(a) // A.echo_жA 未定义<br> // A.echoжA(a) // A.echoжA 未定义<br> A.setX(a, 4)<br> // A.setY(a, 7) // A.setY 未定义<br> println(a.x) // 0<br>

给你参考一下:
https://github.com/yougg/gonote/blob/main/gogrammar.md#%E6%96%B9%E6%B3%95-method

go 的习惯一般是外围判断的,这算是一种约定

go 的方法你理解成 fun Run(a *A)就好了

cpp<br><br>class A {<br>public:<br> void f1() { cout &lt;&lt; "foobar " &lt;&lt; this-&gt;a;}<br><br>private:<br> int a;<br>};<br><br>int main() {<br> A *a = nullptr;<br> a-&gt;f1();<br>}<br>

也不需要判断 this 是不是 NULL 啊,这样是不是好理解多了

这回复楼层不支持 markdown ,真是看的头皮发麻

虽然空值设计确实很麻烦,而且 Go 的 nil 有时候让人感到别扭,但你这个例子……

我更想知道结构体里的 *bool 和 *string 这种东西有没有什么好的解决方案……

struct 指针判断是否为空还是很容易理解,毕竟 c/c++都是如此,golang 不显性的是 interface{} 对象无法通过 != nil 来直接判断是否为空,我也是写了好几个月代码后才发现。

你在(a *A) Run()里面也是可以判断 a 是不是 nil 的。

总有一个人要承担责任,显然调用者是最佳人选。

所以使用 this 指针的时候要不要判断它是不是 null?
要啊! go 又没有异常处理,你得返回一个 error

这个问题其实挺好玩的 理论上是可以弄成类似 static 函数的行为 但是 go 这么写显然会被打

可以调用方法,但是用了结构体里的东西,没初始化就 panic 了

在Golang(Go语言)中,nil 是一个特殊的标识符,用于表示各种引用类型的零值。它常见于指针、通道(channel)、切片(slice)、映射(map)、函数以及接口类型。理解 nil 的行为对于编写健壮的Go程序至关重要。

  1. 指针和通道:当指针或通道未初始化时,它们的值默认为 nil。尝试访问 nil 指针或向 nil 通道发送/接收数据会引发运行时恐慌(panic)。

  2. 切片和映射:切片和映射的零值也是 nil。一个 nil 切片的长度和容量都是0,可以安全地追加元素,而无需先检查是否为 nil。对于 nil 映射,任何读取或写入操作都会首先触发一个隐式的分配,将映射变为非 nil

  3. 接口:接口类型的变量如果未绑定具体类型,则其值为 nil。可以通过类型断言或类型选择(type switch)来检查接口是否包含 nil 或特定类型的值。

  4. 函数:函数类型的零值也是 nil,表示没有函数被赋值。调用 nil 函数会引发恐慌。

处理 nil 时,建议总是进行显式的检查,特别是在进行解引用或操作前,以避免运行时错误。Go语言的类型安全特性要求开发者对 nil 的处理要格外小心,以确保程序的稳定性和可靠性。希望这能帮助你更好地理解Go语言中 nil 的使用!

回到顶部