导读:本期聚焦于小伙伴创作的《Golang多级指针在开发中是否必要?pointer to pointer使用场景说明》,敬请观看详情。函数参数传递时若想修改调用方的指针变量本身,单级指针无法做到,这时候就需要用到指向指针的指针。不少初学者误以为Go里所有修改都用单级指针即可,结果在链表头节点替换、错误返回新指针等场景下踩坑。本文从内存布局讲清二级指针的底层机制,结合链表插入与反射赋值两个真实案例给出代码,并分析何时该用、何时应避免,帮助你在工程里做合理取舍。

在Go语言里,指针本身也是值类型,函数接收单级指针时只是拷贝了地址。如果想让函数内部把调用方的指针变量指向别的对象,就必须传递二级指针,也就是指向指针的指针。理解这一点,才能判断多级指针在业务代码里到底有没有必要。

Golang多级指针在开发中是否必要?pointer to pointer使用场景说明

一、为什么单级指针改不了指针变量本身

Go里所有参数传递都是值拷贝。当你传一个*int给函数,函数拿到的是这个地址的副本。通过副本可以修改它指向的内存,但修改副本自己存的值(也就是让副本指向别处)不会影响原指针变量。下面这段代码能说明问题:

package main

import "fmt"

func changeVal(p *int) {
    *p = 10 // 可以改到原内存
}

func changePtr(p *int) {
    newVal := 20
    p = &newVal // 只改了副本,原变量没变
}

func main() {
    x := 1
    p := &x
    changeVal(p)
    fmt.Println(*p) // 10
    changePtr(p)
    fmt.Println(*p) // 还是10,不是20
}

上面例子中changePtr试图让指针指向新变量,但因为p是值拷贝,函数退出后原p依旧指向x。若真要在函数里更换调用方的指针目标,只能传**int

从内存角度看,二级指针就是一个存着一级指针地址的变量。函数拿到二级指针后,通过*pp拿到原一级指针,再对*pp赋值就能改变原指针的指向。这种机制在C里很常见,Go虽然隐藏了部分底层操作,但语义一致。

二、典型使用场景:链表头节点替换

在实现单链表时,插入或删除头节点经常需要修改头指针。如果头指针是包外传入的变量,用单级指针无法实现头节点切换,二级指针则非常自然。下面用二级指针完成头部插入:

package main

import "fmt"

type Node struct {
    Val  int
    Next *Node
}

// 用二级指针,方便修改调用方的头指针
func insertHead(head **Node, val int) {
    newNode := &Node{Val: val, Next: *head}
    *head = newNode
}

func main() {
    var head *Node
    insertHead(&head, 1)
    insertHead(&head, 2)
    for n := head; n != nil; n = n.Next {
        fmt.Print(n.Val, " ")
    }
    // 输出 2 1
}

这里insertHead接收**Node,通过*head = newNode直接改掉了main里的head变量。如果用单级指针,插入第一个节点后mainhead还是nil,逻辑就错了。

除了链表,树结构的根节点重平衡、连接池里替换整个连接句柄等,也常借助二级指针。核心判断标准是:是否需要在函数内改变调用方指针变量本身的指向。如果是,单级指针不够用。

三、反射赋值中的二级指针

Go的reflect包在给指针指向的对象赋值时,有时需要拿到可寻址的指针地址。当你用reflect.ValueOf(&x)拿到的是reflect.Value类型,其内部是指针值;若要修改该指针指向,可传二级指针给通用函数。示例如下:

package main

import (
    "fmt"
    "reflect"
)

func setViaReflect(ptr interface{}, val int) {
    // ptr 应为 **int 之类
    rv := reflect.ValueOf(ptr).Elem() // 取到一级指针
    rv.Elem().SetInt(int64(val))      // 修改指向的内容
}

func main() {
    x := 0
    p := &x
    setViaReflect(&p, 99)
    fmt.Println(x) // 99
}

上面setViaReflect的参数ptr实际是**intinterface{}。通过Elem两次解引用,最终改到x。这种模式在写通用序列化、配置绑定库时很实用,能避免调用方先取值再回写。

不过反射本身已增加复杂度,再叠二级指针会让代码更难读。若不是写底层库,建议用明确类型的函数代替反射加多级指针。

四、何时应该避免多级指针

业务层代码里,大多数情况不需要二级指针。比如修改结构体字段,传*Struct就够了;返回新对象直接return *Struct比改入参指针更清晰。过度使用二级指针会让调用方写满&p*pp,可读性下降。

// 不推荐:为了改指针而用二级
func badReset(p **int) {
    *p = nil
}

// 推荐:直接返回新指针
func goodReset() *int {
    return nil
}

从工程维护看,二级指针应集中在底层容器、框架或反射工具中。普通CRUD、API层尽量不用,用返回值表达变化更安全,也方便单元测试。

总结来说,Golang多级指针不是语法噱头,它在需要变更指针变量本身时有不可替代的作用,但日常开发大多用不到。先想清是不是要改指针指向,再决定是否上二级指针,能少写很多绕弯代码。

Golang多级指针pointer_to_pointer修改时间:2026-08-06 05:00:27

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。