很多Golang初学者一开始都会混淆函数参数传递的规则,常常遇到修改参数后原始数据变化不符合预期的情况。为了更直观地理解这个机制,我们先看下面这张示意图:

Golang参数传递的核心规则
Golang中所有的函数参数传递都是值传递,也就是说调用函数时,会把实参的值复制一份传递给形参,形参和实参是两个独立的变量,占用不同的内存空间。不过这里需要注意,Golang里的类型分为值类型和引用类型,二者的传递表现会有差异。
值类型的参数传递
值类型包括int、float、bool、string、数组、结构体等,传递时会直接复制整个值的内容。修改形参不会影响原始实参。
package main
import "fmt"
// 定义结构体作为值类型示例
type User struct {
Name string
Age int
}
func modifyUser(u User) {
u.Name = "李四"
u.Age = 20
fmt.Println("函数内修改后:", u) // 输出 {李四 20}
}
func main() {
user := User{Name: "张三", Age: 18}
modifyUser(user)
fmt.Println("函数外原始数据:", user) // 输出 {张三 18},未被修改
}引用类型的参数传递
引用类型包括切片、map、通道、接口、函数等,这些类型的变量本身存储的是一个指向底层数据结构的引用(类似指针)。传递引用类型参数时,复制的是这个引用的值,也就是形参和实参指向同一个底层数据结构。这时候修改形参指向的底层数据,会影响原始实参;但如果修改形参本身(比如给形参重新赋值),不会影响原始实参。
package main
import "fmt"
func modifySlice(s []int) {
s[0] = 100 // 修改底层数据,会影响原始切片
s = append(s, 200) // 给形参重新赋值,不会影响原始切片
fmt.Println("函数内切片:", s) // 输出 [100 2 3 200]
}
func main() {
slice := []int{1, 2, 3}
modifySlice(slice)
fmt.Println("函数外原始切片:", slice) // 输出 [100 2 3],append的结果未生效
}指针传递的本质
有时候我们想要在函数内修改原始变量本身,这时候可以传递指针。指针本身也是一个值类型,传递的是指针变量的拷贝,但是这个拷贝和原始指针指向同一个内存地址,所以通过指针可以修改原始变量的值。
package main
import "fmt"
func modifyByPointer(num *int) {
*num = 200 // 通过指针修改指向的内存值
num = nil // 修改形参指针本身,不影响原始指针
}
func main() {
a := 100
p := &a
fmt.Println("修改前a的值:", a, "p指向的地址:", p) // 输出 100 和具体地址
modifyByPointer(p)
fmt.Println("修改后a的值:", a, "p指向的地址:", p) // 输出 200 和原来的地址,p未被修改
}常见误区说明
- 不要误以为切片、map传递是引用传递,本质是值传递,传递的是引用的拷贝
- 修改参数本身(比如给参数重新赋值)不会影响原始变量,只有修改参数指向的底层数据才会有影响
- 如果需要让函数修改原始变量本身,优先使用指针传递,比传递整个值类型更高效
Golang的设计中只保留了值传递,这样参数传递的行为更明确,开发者可以清晰判断修改是否会影响原始数据,减少隐藏的bug。
理解了Golang的参数传递规则后,写代码时就能准确预判函数调用对原始数据的影响,避免不必要的错误。平时开发中可以多结合代码示例验证不同场景的表现,加深理解。