请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

对nil的疑惑

老师你好,视频中有这么一段代码:

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,这样不是更好吗?

正在回答 回答被采纳积分+3

1回答

ccmouse 2022-10-08 16:32:24

这些问题都很好,值得探讨。

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。


2 回复 有任何疑惑可以回复我~
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信