0

0

Go语言指针接收器深度解析:理解引用与赋值的陷阱

霞舞

霞舞

发布时间:2025-11-08 14:45:12

|

805人浏览过

|

来源于php中文网

原创

Go语言指针接收器深度解析:理解引用与赋值的陷阱

go语言中,指针接收器常用于修改结构体实例的状态。然而,当涉及到修改结构体内部的指针字段时,直接对局部指针变量赋值可能无法达到预期效果。本文将通过二叉搜索树的插入操作为例,深入剖析这一常见陷阱,并详细介绍如何利用二级指针(即指向指针的指针)的概念,通过取地址和解引用操作,实现对原始结构体指针字段的正确更新。

理解Go语言中的指针与赋值

在Go语言中,变量赋值行为的核心是值拷贝。当我们将一个变量赋值给另一个变量时,实际上是复制了该变量的。对于指针类型而言,其“值”就是它所指向的内存地址。

考虑以下简单的二叉搜索树(BST)结构:

package main

import "fmt"

// Node 定义二叉树节点
type Node struct {
    key   int
    left, right *Node
}

// NewNode 创建一个新节点
func NewNode(key int) *Node {
    return &Node{key, nil, nil}
}

// BST 定义二叉搜索树
type BST struct {
    root *Node
}

// NewBinarySearchTree 创建一个空的二叉搜索树
func NewBinarySearchTree() *BST {
    return &BST{nil}
}

// inorder 中序遍历
func inorder(node *Node) {
    if node == nil {
        return
    }
    inorder(node.left)
    fmt.Print(node.key, " ")
    inorder(node.right)
}

func main() {
    tree := NewBinarySearchTree()
    tree.Insert(3)
    tree.Insert(1)
    tree.Insert(2)
    tree.Insert(4)
    fmt.Print("原始插入方法结果: ")
    inorder(tree.root) // 1 2 3 4
    fmt.Println()

    tree2 := NewBinarySearchTree()
    tree2.Insert2(3) // 尝试使用简化版插入方法
    tree2.Insert2(1)
    tree2.Insert2(2)
    fmt.Print("Insert2 方法结果: ")
    inorder(tree2.root) // 预期是 1 2 3,实际为空
    fmt.Println()

    tree3 := NewBinarySearchTree()
    tree3.Insert3(3) // 使用修正后的方法
    tree3.Insert3(1)
    tree3.Insert3(2)
    tree3.Insert3(4)
    fmt.Print("Insert3 方法结果: ")
    inorder(tree3.root) // 1 2 3 4
    fmt.Println()
}

上述代码中的 BST.Insert 方法是正确的插入方式,它通过迭代找到合适的插入位置并直接修改 node.left 或 node.right 字段。

为什么简化版 Insert2 会失败?

问题出在尝试简化 Insert 方法时,通常会写出类似 Insert2 的版本:

立即学习go语言免费学习笔记(深入)”;

func (t *BST) Insert2(key int) {
    var node *Node
    node = t.root // 1. node 复制了 t.root 的值 (即 nil)
    for node != nil { // 2. 第一次插入时,t.root 为 nil,循环跳过
        if key < node.key {
            node = node.left
        } else {
            node = node.right
        }
    }
    node = NewNode(key) // 3. 此时,node 被赋值为一个新节点的地址
    // t.root 仍然是 nil,未被更新
}

让我们详细分析 Insert2 的执行流程:

  1. node = t.root: 这一步将 t.root 的当前值(在第一次插入时为 nil)复制给局部变量 node。此时,node 和 t.root 都指向 nil。
  2. for node != nil: 由于 node 是 nil,循环体被跳过。
  3. node = NewNode(key): 这一步将新创建节点的地址赋值给局部变量 node。此时,node 指向了新节点,但 t.root 仍然是 nil。

关键在于,node = t.root 只是让 node 指向了 t.root 所指向的同一个地址。当 node 随后被重新赋值为 NewNode(key) 时,它只是改变了局部变量 node 自己所指向的地址,而没有影响到 t.root 这个变量本身。这就像你有一张纸条写着“A地址”,我复制了这张纸条,我的纸条也写着“A地址”。如果我把我的纸条上的内容改成“B地址”,你的纸条仍然写着“A地址”,并没有改变。

萝卜简历
萝卜简历

免费在线AI简历制作工具,帮助求职者轻松完成简历制作。

下载

要修改 t.root,我们必须直接对 t.root 进行赋值,或者通过一个能够“访问并修改 t.root 变量本身”的机制。

解决方案:使用指向指针的指针

为了修改 t.root、node.left 或 node.right 这些指针变量本身,我们需要一个指向这些指针变量的指针。在Go语言中,这意味着我们需要一个 **Node 类型(指向 *Node 的指针)。

我们可以通过 & 运算符获取一个变量的地址。例如,&t.root 会得到 t.root 变量本身的内存地址,其类型是 **Node。

修正后的 Insert3 方法如下:

func (t *BST) Insert3(key int) {
    nodePtr := &t.root // 1. nodePtr 现在指向了 t.root 变量的内存地址 (类型是 **Node)

    for *nodePtr != nil { // 2. 解引用 nodePtr,检查它所指向的 *Node 变量是否为 nil
        if key < (*nodePtr).key { // 3. 解引用 nodePtr 得到 *Node,再访问其 key 字段
            nodePtr = &(*nodePtr).left // 4. nodePtr 更新为指向当前节点左子指针变量的地址
        } else {
            nodePtr = &(*nodePtr).right // 5. nodePtr 更新为指向当前节点右子指针变量的地址
        }
    }
    *nodePtr = NewNode(key) // 6. 解引用 nodePtr,将其所指向的 *Node 变量赋值为新节点
}

让我们再次详细分析 Insert3 的执行流程:

  1. nodePtr := &t.root: nodePtr 不再是一个 *Node 类型,而是一个 **Node 类型。它存储的是 t.root 变量在内存中的地址。
  2. for *nodePtr != nil: 这里 *nodePtr 对 nodePtr 进行解引用。这意味着我们访问的是 nodePtr 所指向的那个 *Node 变量(即 t.root)。我们检查 t.root 是否为 nil。
  3. key
  4. nodePtr = &(*nodePtr).left / nodePtr = &(*nodePtr).right: 这是核心步骤。(*nodePtr) 得到当前的 *Node 变量(例如,如果 nodePtr 指向 t.root,那么 (*nodePtr) 就是 t.root)。然后,我们访问这个 *Node 变量的 left 或 right 字段,这个字段本身又是一个 *Node 类型的变量。最后,& 运算符获取这个 left 或 right 字段变量的地址,并将其赋值给 nodePtr。这样,nodePtr 就始终指向了我们想要修改的那个 *Node 变量的地址。
  5. *nodePtr = NewNode(key): 当循环结束时,nodePtr 存储的是我们最终找到的那个 nil 指针变量(可能是 t.root,也可能是某个节点的 left 或 right 字段)的地址。通过 *nodePtr = NewNode(key),我们解引用 nodePtr,直接对它所指向的那个 *Node 变量进行赋值,将其更新为新创建的节点。

总结与注意事项

  • 值拷贝的理解:Go语言中的赋值操作总是值拷贝。对于指针类型,拷贝的是指针本身存储的内存地址。
  • 修改指针变量本身:如果你想修改一个结构体字段(例如 t.root 或 node.left)所指向的地址,你需要获取该字段变量的地址(使用 & 运算符),然后通过这个“指向指针的指针”来间接修改它。
  • *& 和 `` 的作用**:
    • & (取地址运算符):获取一个变量的内存地址。例如,&t.root 获取 t.root 变量本身的地址。
    • * (解引用运算符):访问指针所指向的内存地址中的值。例如,*nodePtr 访问 nodePtr 所指向的 *Node 变量。在赋值语句的左侧使用时,它表示修改指针所指向的值。
  • 适用场景:这种“指向指针的指针”模式在需要动态修改链表、树等数据结构中的连接(即指针字段)时非常有用,因为它允许你抽象出要修改的具体指针变量,并在循环中灵活地更新它。

通过深入理解Go语言的指针机制以及 & 和 * 运算符的精确用法,我们可以避免在处理复杂数据结构时常见的指针陷阱,并编写出更加健壮和高效的代码。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1492

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

230

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

86

2025.10.17

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

197

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

190

2025.07.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

536

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

23

2026.01.06

c++空格相关教程合集
c++空格相关教程合集

本专题整合了c++空格相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
HTML5/CSS3/JavaScript/ES6入门课程
HTML5/CSS3/JavaScript/ES6入门课程

共102课时 | 6.8万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 19万人学习

第二十二期_前端开发
第二十二期_前端开发

共119课时 | 12.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号