在Go语言中,函数参数传递采用的是值传递机制。当我们把一个变量传给函数时,函数接收到的是这个变量的一个副本,而不是变量本身。如果希望函数内部对参数的修改能够反映到函数外部的原有变量上,就必须使用指针。指针保存的是变量的内存地址,通过地址可以找到并修改原始数据。理解这一点是掌握Go指针修改函数外变量的核心。

为什么普通参数无法修改函数外变量
Go里的所有参数传递都是值拷贝。对于int、float64这类基础类型,拷贝的是数值本身;对于结构体,拷贝的是结构体的全部字段。函数内部对参数的任何赋值,都只是在修改局部副本,调用方的原变量丝毫不受影响。
下面这段代码展示了问题所在。我们在modify函数里把num改成了100,但主函数打印出来仍然是10。原因就是modify接收的是num的副本,不是原来的num。
package main
import "fmt"
func modify(num int) {
num = 100
}
func main() {
num := 10
modify(num)
fmt.Println(num) // 输出 10
}
如果想让修改生效,就要把num的地址传进去,让函数通过地址去操作原内存。这就引出了指针参数的用法。
使用指针参数修改基础类型变量
把变量地址用取地址符&传给函数,函数形参声明为指针类型,例如*int。在函数内部通过解引用*ptr来赋值,就能直接改动外部变量。
如下示例,modifyByPtr接收*int,对它解引用后赋值为100。主函数里的num因为地址被传入,所以值也被改成了100。这是Go中最基础的指针引用技巧。
package main
import "fmt"
func modifyByPtr(ptr *int) {
*ptr = 100
}
func main() {
num := 10
modifyByPtr(&num)
fmt.Println(num) // 输出 100
}
这种方式在需要返回多个计算结果、或者修改配置对象字段时非常实用。注意指针不能为nil,否则解引用会触发panic,调用前要确保变量已声明。
结构体与指针参数的引用技巧
结构体如果直接传值,函数内改字段不会影响外部。传结构体指针则可以用->语法(Go中用点号配合指针自动解引用)修改原对象。Go允许对指针结构体直接用点号访问字段,编译器会自动解引用。
下面例子中,updateUser接收*User,修改Name和Age,主函数打印发现原user已被改变。如果写成传值User,修改就仅限函数内。
package main
import "fmt"
type User struct {
Name string
Age int
}
func updateUser(u *User) {
u.Name = "李四"
u.Age = 30
}
func main() {
u := User{Name: "张三", Age: 20}
updateUser(&u)
fmt.Println(u) // 输出 {李四 30}
}
在大型项目中,为避免不必要的大结构体拷贝,通常都会用指针传参。同时,若方法需要修改接收者状态,也应把方法接收者声明为指针类型。
切片和映射的指针使用误区
切片和映射底层包含指向数组或哈希表的指针,所以传值也能修改元素。但如果你要更换切片本身(如append后重新分配),或让外部变量指向新切片,仍需传*[]T。很多开发者误以为切片传参一定能改变外部长度,其实只改元素才行。
下例展示用指针切片参数让外部变量获得新的append结果。若不用指针,append可能导致外部切片头部不变。
package main
import "fmt"
func appendVal(s *[]int, v int) {
*s = append(*s, v)
}
func main() {
sl := []int{1, 2}
appendVal(&sl, 3)
fmt.Println(sl) // 输出 [1 2 3]
}
映射由于引用语义,通常直接传map就能改键值,但要把map变量重新赋值成别的map时,同样需要指针或返回新map。明确语义可避免隐蔽bug。
常见避坑点与最佳实践
第一,不要返回局部变量地址,Go虽有逃逸分析会把它分配到堆,但逻辑上仍应避免混乱。第二,指针参数判空很重要,防止nil解引用崩溃。第三,若变量很小,传指针未必比传值快,要结合场景。
汇总技巧:基础类型改值用*T;结构体改字段用*Struct;切片重分配用*[]T;映射重绑定用*map[K]V。保持函数职责单一,指针参数命名带ptr或p前缀可提升可读性。
| 场景 | 参数类型 | 能否改外部 |
|---|---|---|
| 改int值 | *int | 能 |
| 改结构体字段 | *User | 能 |
| 改切片元素 | []int | 能 |
| 重分配切片 | *[]int | 能 |
掌握上述Go Pointer参数引用方式,你就能在函数间安全高效地共享并修改状态,写出更清晰的Go代码。