为什么在Go语言中访问空指针会导致中序遍历错误?

huangapple go评论76阅读模式
英文:

Why does accessing a nil pointer in Go cause an Error in Inorder traversal

问题

我已经用Python编写了一个正常工作的中序遍历函数。

def inOrderTraverse(tree, array):
    if tree is None:
        return None
    inOrderTraverse(tree.left, array)
    array.append(tree.value)
    inOrderTraverse(tree.right, array)
    return array

然而,当我尝试使用相同的逻辑在Go语言中实现时,它不起作用。

type BST struct {
    Value int
    Left  *BST
    Right *BST
}

func (tree *BST) InOrderTraverse(array []int) []int {
    if tree == nil {
        return nil
    }
    (tree.Left).InOrderTraverse(array)
    array = append(array, tree.Value)
    (tree.Right).InOrderTraverse(array)
    return array
}

为了修复这个问题,我编写了一些if语句,以防止在tree.Left为nil时调用函数。然而,我仍然困惑于为什么我的原始代码不起作用。

func (tree *BST) InOrderTraverse(array []int) []int {
    if tree.Left != nil {
        array = tree.Left.InOrderTraverse(array)
    }
    array = append(array, tree.Value)
    if tree.Right != nil {
        array = tree.Right.InOrderTraverse(array)
    }
    return array
}

总结一下,我试图使用相同的思路在Go语言中编写中序遍历函数,但它不起作用。我找到了解决方法(第三个代码块),但我仍然不明白为什么第二个代码块不起作用。似乎在空指针上调用函数会导致错误。

英文:

I have written inorder traversal in python which works fine.

def inOrderTraverse(tree, array):
    if tree is None:
		return None
	inOrderTraverse(tree.left,array)
	array.append(tree.value)
	inOrderTraverse(tree.right,array)
	return array

However when I try to apply the same logic with GOlang, it does not work.

type BST struct {
	Value int

	Left  *BST
	Right *BST
}

func (tree *BST) InOrderTraverse(array []int) []int {
	if tree == nil {
		return nil
	}
	(tree.Left).InOrderTraverse(array)
	array = append(array,tree.Value)
	(tree.Right).InOrderTraverse(array)
	return array
}

In order to fix this I write if statements to prevent the functions from being called if tree.Left is nil. However I am still confused why my original code does not work.

func (tree *BST) InOrderTraverse(array []int) []int {
	if tree.Left != nil {
		array = tree.Left.InOrderTraverse(array)
	}
	array = append(array,tree.Value)
	if tree.Right != nil {
		array = tree.Right.InOrderTraverse(array)
	}
	return array
}

To summarize, I am trying to write inorder traversal in Go using the same reasoning from python and it is not working. I figured out how to get it to work(the third code block), however I still do not understand why the second code block does not work. It seems like calling the function on the Nil pointer causes an error

答案1

得分: 3

几件事情:

在你的第二个代码块中,它不起作用的原因是因为结果被丢弃了:

 // (tree.Left).InOrderTraverse(array) // 忽略了返回结果
 array = (tree.Left).InOrderTraverse(array) // 这样捕获它

Python 使用按引用传递;Go 使用按值传递。因此,当在 Go 中修改像 slices 这样的东西时,可能会变得棘手。

由于你已经返回了遍历的结果,传入 array 实际上是多余的。


将所有这些整合在一起,修复左右遍历,并使用返回变量 result

func (tree *BST) InOrderTraverse() (result []int) {
    if tree == nil {
        return // 隐式返回空的 `result`
    }

    result = append(result, (tree.Left).InOrderTraverse()...)
    result = append(result, tree.Value)
    result = append(result, (tree.Right).InOrderTraverse()...)

    return // 隐式返回 `result`
}

工作示例:https://play.golang.org/p/AUvgZTABiU9


编辑

一个 Go"按引用传递" 实现 - 在调用之间传递 results 切片 - 看起来与你的 python 实现非常相似:

func (tree *BST) InOrderTraverse(result *[]int) {
	if tree == nil {
		return
	}

	(tree.Left).InOrderTraverse(result)

	*result = append(*result, tree.Value)

	(tree.Right).InOrderTraverse(result)
}

https://play.golang.org/p/Pp9-4-y-lmE

注意:这里需要 *[]int(即指向 []int 的指针),因为在递归过程中切片的容量会发生变化。切片实际上只是一个带有后备数组的标头 - 因此,虽然函数可以更改切片的元素(即使按值复制),但它不能使切片收缩或增长(正如在这里的情况)。传递指针允许将切片重新分配给一个可能更大的后备数组。

英文:

A couple of things:

In your 2nd block of code, the reason it does not work is because the result is discarded:

 // (tree.Left).InOrderTraverse(array) // the return result is ignored
 array = (tree.Left).InOrderTraverse(array) // capture it like so

Python uses pass by-reference; Go passes by value. As such it can get tricky especially when modifying things like slices in Go.

Since you're already returning the results of the traversal, passing in the array is actually redundant.


Pulling this all together to fix both left & right traversal - and using a return variable result:

func (tree *BST) InOrderTraverse() (result []int) {
    if tree == nil {
        return // implicitly returns empty `result`
    }

    result = append(result, (tree.Left).InOrderTraverse()...)
    result = append(result, tree.Value)
    result = append(result, (tree.Right).InOrderTraverse()...)

    return // implicitly returns `result`
}

Working example: https://play.golang.org/p/AUvgZTABiU9


EDIT

A Go "pass by reference" implementation - where the results slice is passed between calls - looks very similar to your python implementation:

func (tree *BST) InOrderTraverse(result *[]int) {
	if tree == nil {
		return
	}

	(tree.Left).InOrderTraverse(result)

	*result = append(*result, tree.Value)

	(tree.Right).InOrderTraverse(result)
}

https://play.golang.org/p/Pp9-4-y-lmE

Note: *[]int (i.e. a pointer to []int) is needed here, as the slice capacity will change during the recursion. A slice is really just a header with a backing array - so while a function can change a slice's elements - even when copied by value - it cannot make the slice shrink or grow (as is the case here). Passing a pointer allows the slice to be reassigned to a potentially larger backing array.

huangapple
  • 本文由 发表于 2021年8月26日 04:40:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/68929598.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定