如何在 Go 中正确通过指针参数初始化结构体指针变量

go 函数参数按值传递,即使传入的是指针(如 `*mgo.session`),函数内对其赋值仅修改副本;要真正更新原始变量,必须传入指向该指针的指针(即 `**mgo.session`),并在函数内解引用赋值。

在 Go 中,所有函数参数都是按值传递的——这意味着传入函数的不是变量本身,而是它的副本。这一点对指针类型尤其容易产生误解:虽然 *T 是一个指针,但 *T 本身仍是一个值(存储着内存地址的变量),它在函数调用时也会被复制。

以原始代码为例:

func ConnectToMongo(session *mgo.Session) {
    session, err = mgo.Dial("localhost:27028") // ❌ 只修改了参数副本,不影响 main 中的 session
}

这里 session 是 *mgo.Session 类型的形参,其值(即 nil 地址)被复制进函数。后续 session = ... 只是让这个局部副本指向新地址,而 main 中的 session 变量依然为 nil。

✅ 正确做法是使用双重指针(**mgo.Session),让函数能修改原始指针变量的值:

func ConnectToMongo(session **mgo.Session) {
    if *session == nil { // 注意:此处应检查 *session,而非 session(后者是 **mgo.Session,不可能为 nil 除非传入 nil 地址)
        var err error
        *session, err = mgo.Dial("localhost:27028")
        if err != nil {
            panic(err)
        }
    }
}

func main() {
    var session *mgo.Session
    ConnectToMongo(&session) // 传入 session 变量的地址
    if session == nil {
        fmt.Println("nil. Why?") // 不会执行
    } else {
        fmt.Println("Connected successfully.")
        defer session.Close() // 记得关闭连接
    }
}

⚠️ 关键注意事项:

  • if session == nil 在 **mgo.Session 函数中是检查“指针的指针是否为空”,而非“目标指针是否为空”——逻辑错误;应改为 if *session == nil。

  • &session 获取的是 *mgo.Session 类型变量的地址,类型为 **mgo.Session,与函数签名严格匹配。

  • 现代 Go 推荐更清晰、更符合惯用法的写法:直接返回值,而非依赖多重指针副作用:

    func ConnectToMongo() (*mgo.Session, error) {
        return mgo.Dial("localhost:27028")
    }
    
    func main() {
        session, err := ConnectToMongo()
        if err != nil {
            panic(err)
        }
        defer session.Close()
    }

    这种方式语义明确、易于测试、避免隐式状态修改,是更健壮和可维护的设计。

总结:Go 中无法通过单级指针参数“输出”新对象地址;若必须使用参数输出模式,务必使用 **T 并正确解引用 *session = newValue;但优先推荐返回值方式,兼顾简洁性与安全性。