对nil的疑惑
来源:4-1 结构体和方法

慕莱坞0998854
2022-10-07
老师你好,视频中有这么一段代码:
type treeNode struct {
value int
}
func (node *treeNode) setValue(value int) {
if node == nil {
fmt.Println("setting value to nil node,ignored")
// 很神奇的是 假如我们没写下面的return语句
// 在go run 的时候会报错
// 但是 go build 不会报错 而是在执行生成的exe文件的时候会报错
return
}
node.value = value
}
func main() {
var pRoot *treeNode
pRoot.setValue(200)
}
作为一个C语言学的不好,只接触过JS/TS的人,我对这段代码有3点疑惑
1> nil是空指针的意思嘛?我搜索了一下,好像是指 指针变量还没确定到底指向谁的时候,这样的指针变量为空指针,C语言中常见于int *p = NULL
,其值为00000000 ?
2> 老师在视频里面说,当使用指针作为接收者的时候,并不是每次都要判断是否都要为nil的,可是实际上,在本节的代码中,traverse函数以及在下一节的视频(扩展已有结构)中,也是判断了指针接受者是否为nil的,我现在看到指针类型变量就心头一紧,我怎么感觉好像都需要判断是否为nil的情况呢?
3> 第三个问题最疑惑,如上的注释的部分所示,老师最开始在视频中忘记写了return, 但是,编辑器并没有提示错误,而是在运行的go run xxx.go 的时候才报错了,假如我们在TS中写null.setValue()
或者
type Foo = Bar | Null
const someFn = (param:Foo) => {
// 编辑器马上就可以提示你param可能为Null
param.someMethod()
}
这样一对比,明显是TS这种在你写代码的时候就提示风险的方式更好啊,为什么Go语言没有报错呢?
在traverse函数中
func (node *TreeNode) Traverse() {
if node == nil {
return
}
// 老师说,假如是在java或者c++中,需要写成
if(node.Left != nil){
node.Left.Traverse()
}
node.Print()
node.Right.Traverse()
}
对于上面的写法,老师的原话是: “nil也可以啊,它只是一个普通的函数啊,你只要判断了就行”,老师的意思是说,在这个函数开头我们就判断了node == nil的情况,所以下面直接写node.Left.Traverse(),此时node.Left就不可能为nil了,所以我们不需要这个If判断了,而C++/Java可能更严谨?所以需要我们再使用if语句来确保node.Left不为nil? 是这个意思吗 ? 相对来说我怎么感觉加个判断更好呢?或者说在TS里面:
type TreeNode = {
value: number
left: TreeNode | null
right: TreeNode | null
}
使用!或者?操作符
node.left!.Traverse()
// or
node.left?.Traverse()
这样也行啊,总的来说,我还是比较喜欢TS这种在你写代码的时候就可以提示你可能为null的写法,我暂时还感受不到为什么Go语言要弄成这种 写起来好像更简洁 但是实际上你在写的时候就要提醒自己考虑是否为nil的样子,多写了一点这个变量类型可能为null这一点点代码,你写的时候编辑器提醒你可能为null,这样不是更好吗?
1回答
-
ccmouse
2022-10-08
这些问题都很好,值得探讨。
1> nil是空指针的意思嘛?它的值是多少?
Go语言比C更抽象一些,指针就是指向某个变量的一种类型。nil就是不指向任何变量,不过它的具体数值的确是0。
2> 老师在视频里面说,当使用指针作为接收者的时候,并不是每次都要判断是否都要为nil的。
这里的原则是:如果我这个方法里nil是合法的,那应该要判断nil。否则不需要判断。合法的场合,比如视频里的*TreeNode例子,我允许nil传入,因此需要判断。很多其它场合下,我们无法处理nil的情况, 比如http标准库的发送请求:
func (c *Client) Do(req *Request) (*Response, error) {
return c.do(req)
}
这里我们无法处理nil的情况,当然就不需要检查,因为我们本来这里不支持nil,检查了除了panic也不能做什么。
3> 第三个问题最疑惑,什么时候报错比较好
每一种设计都有它的优劣,不需要排出一个好坏。
ts里,null即是值也是类型。TreeNode | null和TreeNode是两个类型,TreeNode类型不可能是null,null类型才可以是null。有了这层信息,编译器就能判断代码是否出错。Go里,nil是值而不是类型,因此编译时无法判断这里该不该加return。那么增加了null类型使得代码更加健壮,但是类型系统更复杂,也增加了编译耗时,Go语言选择不支持。
Java/C++中,null的判断要写在调用Traverse之前。不过仔细想来:
if(node.Left != nil){
node.Left.Traverse()
}
在这块代码中,nil是node.Left的合法的值。合法的值我们就要判断。Java/C++中,null的判断要写在调用Traverse之前,完全是因为这样的判断不可以写在Traverse里面。但是Go可以,这样判断写在Traverse里面的话,我们获得了一个完备的Traverse方法的实现。事实上也是判断写在里面,代码更为简洁。当然,每一种设计都有优劣,这里的劣势,就是会犯一些之前不常见的错误,比如我忘了return。
20
相似问题