
本文深入探讨go语言中指针接收器更新变量时常见的误区,特别是当尝试通过局部指针变量修改结构体字段时为何不生效。通过分析二叉搜索树的插入操作,文章阐明了go语言中指针赋值与通过指针间接修改变量的本质区别,并提出了一种使用多一级指针间接(即指向指针的指针)来正确更新目标变量的解决方案,确保结构体字段能被有效修改。
在Go语言中,指针是一种特殊类型,它存储了另一个变量的内存地址。通过指针,我们可以间接访问和修改其指向的变量。当一个函数或方法接收一个指针作为参数或接收器时,它能够修改该指针所指向的底层数据。然而,一个常见的误解是,对局部指针变量的重新赋值,会影响到它最初引用的外部变量。
考虑以下二叉搜索树(BST)的简化插入方法 Insert2:
package main
import "fmt"
type Node struct {
key int
left, right *Node
}
func NewNode(key int) *Node {
return &Node{key, nil, nil}
}
type BST struct {
root *Node
}
func NewBinarySearchTree() *BST {
return &BST{nil}
}
// 原始的 Insert 方法(有效)
func (t *BST) Insert(key int) {
if t.root == nil {
t.root = NewNode(key)
return
}
var node = t.root
for {
if key < node.key {
if node.left == nil {
node.left = NewNode(key)
return
} else {
node = node.left
}
} else {
if node.right == nil {
node.right = NewNode(key)
return
} else {
node = node.right
}
}
}
}
// 简化后的 Insert2 方法(无效)
func (t *BST) Insert2(key int) {
var node *Node
node = t.root // 1. node 指向 t.root 所指向的内存地址
for node != nil {
if key < node.key {
node = node.left // 2. node 被重新赋值,指向 node.left 所指向的内存地址
} else {
node = node.right // 3. node 被重新赋值,指向 node.right 所指向的内存地址
}
}
node = NewNode(key) // 4. node 被再次重新赋值,指向新创建节点的内存地址
}
func inorder(node *Node) {
if node == nil {
return
}
inorder(node.left)
fmt.Print(node.key, " ")
inorder(node.right)
}
func main() {
tree := NewBinarySearchTree()
tree.Insert2(3) // 尝试使用 Insert2
tree.Insert2(1)
tree.Insert2(2)
tree.Insert2(4)
inorder(tree.root) // 输出为空或不符合预期
}在上述 Insert2 方法中,初次调用时,如果 t.root 为 nil:
这里的关键问题在于:node = NewNode(key) 仅仅改变了局部变量 node 所指向的内存地址,使其指向新创建的节点。它并没有修改 t.root(或其他任何 node.left 或 node.right 字段)所指向的地址。因此,t.root 仍然保持为 nil,树结构未被更新。
立即学习“go语言免费学习笔记(深入)”;
要正确地更新 t.root 或 node.left/node.right 这样的 *Node 类型变量,我们需要一个指向这些变量本身的指针。换句话说,如果 t.root 的类型是 *Node,那么我们需要一个类型为 **Node 的指针来修改 t.root。
我们可以通过在遍历过程中始终持有一个指向“待更新指针变量”的指针来实现这一点。当找到合适的插入位置时,这个“待更新指针变量”可能是 t.root、某个节点的 left 字段,或者某个节点的 right 字段。
以下是修正后的 Insert3 方法:
func (t *BST) Insert3(key int) {
// node 现在是一个指向 *Node 类型的指针(即 **Node 类型)
// 它最初指向 t.root 变量本身的内存地址
node := &t.root
// 循环条件:*node != nil 检查的是当前 *node 所指向的 Node 对象是否为 nil
// 也就是检查 t.root, node.left, 或 node.right 是否为 nil
for *node != nil {
if key < (*node).key { // 访问当前 Node 的 key
// node 被重新赋值,使其指向当前节点左子指针变量的内存地址
node = &(*node).left
} else {
// node 被重新赋值,使其指向当前节点右子指针变量的内存地址
node = &(*node).right
}
}
// 循环结束时,*node 是 nil,表示找到了插入位置。
// *node = NewNode(key) 会将新创建的节点赋值给 node 当前指向的 *Node 变量。
// 这个变量可能是 t.root,也可能是某个 Node 的 left 或 right 字段。
*node = NewNode(key)
}
func main() {
tree := NewBinarySearchTree()
tree.Insert3(3) // 使用 Insert3
tree.Insert3(1)
tree.Insert3(2)
tree.Insert3(4)
inorder(tree.root) // 预期输出: 1 2 3 4
}让我们详细解析 Insert3 的关键步骤:
node := &t.root:
*`for node != nil`**:
*`if key < (node).key { node = &(node).left } else { node = &(node).right }`**:
*`node = NewNode(key)`**:
通过深入理解Go语言中指针的赋值行为和间接引用机制,我们可以避免常见的陷阱,并编写出更健壮、更符合预期的代码。
以上就是深入理解Go语言指针接收器与变量更新机制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号