
go 中函数参数按值传递,即使传入指针,也只是该指针值的副本;若需在函数内修改原始指针变量本身(如使其指向新分配的对象),必须传入指向指针的指针(即 `**t`),并在函数内解引用赋值。
在 Go 语言中,所有函数参数都是按值传递的——这意味着当你把一个变量(包括指针)传给函数时,函数接收到的是该值的一个副本。这与 C/C++ 中“传指针可修改原值”的直觉类似,但有一个关键区别:Go 中的指针本身也是值。因此,func f(p *T) 接收的是 *T 类型值的副本,对 p 的重新赋值(如 p = &x)只会影响副本,不会影响调用方的原始指针变量。
回到你的代码问题:
var session *mgo.Session ConnectToMongo(session) // 传入的是 nil 指针的副本
此时 ConnectToMongo 内部的 session 参数只是原始 session 变量的一个拷贝。即使你在函数内执行 session, err = mgo.Dial(...),也只是改变了这个局部副本的值,调用结束后,main 中的 session 依然为 nil。
✅ 正确做法是:使用双指针(**mgo.Session),让函数能修改原始指针变量所存储的地址:
func ConnectToMongo(session **mgo.Session) {
if *session == nil { // 注意:这里检查的是 *session,而非 session
var err error
*session, err = mgo.Dial("localhost:27028") // ✅ 解引用后赋值到原始变量
if err != nil {
panic(err)
}
}
}
func main() {
var session *mgo.Session
ConnectToMongo(&session) // 传入 session 变量的地址(即 **mgo.Session)
if session == nil {
fmt.Println("nil. Why?") // 不会执行
} else {
fmt.Println("Connected successfully.") // ✅ 此时 session 已被正确初始化
}
}⚠️ 关键细节提醒:
- if session == nil 在 **T 函数中检查的是指针的指针是否为空(即 &session 是否为 nil),而我们要判断的是目标指针是否为空,所以应写 if *session == nil;
- 调用时务必使用 &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() // 记得关闭连接
fmt.Println("Connected.")
}这种返回式设计更清晰、更易测试、更符合 Go 的惯用法(Explicit is better than implicit),应优先采用。仅在需要复用已有变量或满足特定接口约束时,才考虑双指针方案。










