参数传递
Go 的参数传递方式取决于类型:基本类型传副本,指针传地址,slice 和 map 传的是底层结构的句柄。
1. 值传递
Go 语言中最基础的传递方式是值传递:函数拿到的是参数的副本,在函数内部修改参数不会影响原始变量。
基本示例
go
package main
import "fmt"
func changeValue(x int) {
x = 100
}
func main() {
num := 5
changeValue(num)
fmt.Println("num:", num) // 输出: num: 5,未被修改
}要点
- 整型、浮点型、布尔型、字符串 等基本类型始终使用值传递
- 函数内部的修改不会影响外部变量
- 每次函数调用都会分配新栈帧,副本独立存在
2. 指针传递
为了能在函数内部修改原始变量,可以使用指针传递。通过传递指针,函数可以访问并修改指针所指向的原始值。
基本示例
go
package main
import "fmt"
func changePointer(x *int) {
*x = 100
}
func main() {
num := 5
changePointer(&num)
fmt.Println("num:", num) // 输出: num: 100,已被修改
}要点
- 使用
&var获取变量的地址作为参数传递 - 在函数内部使用
*ptr访问并修改原始值 - 对于大对象或需要修改原值的场景,推荐使用指针传递
3. slice 和 map 的“传递特性”
slice 和 map 在 Go 中同样传值,只是传的值是底层数据结构的句柄,函数内部对 slice 或 map 的修改会反映在原始值上。
Slice 传递示例
go
package main
import "fmt"
func modifyElements(s []int) {
s[0] = 100 // 修改元素会反映到原始 slice
}
func appendValue(s []int) []int {
return append(s, 4) // append 可能创建新的底层数组,因此需要返回新 slice
}
func main() {
// 方式1:修改元素(不重新分配),无需返回值
s1 := []int{1, 2, 3}
modifyElements(s1)
fmt.Println("修改元素后:", s1) // 输出: 修改元素后: [100 2 3]
// 方式2:append 可能重新分配底层数组,需要返回新 slice
s2 := []int{1, 2, 3}
s3 := appendValue(s2)
fmt.Println("append 后原 slice:", s2) // 输出: append 后原 slice: [1 2 3]
fmt.Println("append 后新 slice:", s3) // 输出: append 后新 slice: [1 2 3 4]
}Map 传递示例
go
package main
import "fmt"
func modifyMap(m map[string]int) {
m["key"] = 100 // 修改会反映在原始 map 上
// 或者添加新键
m["new_key"] = 200
}
func main() {
original := make(map[string]int)
original["existing"] = 50
modifyMap(original)
fmt.Println("map:", original) // 输出: map: map[existing:50 key:100 new_key:200]
}要点
- slice 和 map 的值包含指针到底层数据结构的引用
- 修改 slice 元素或 map 内容 会影响原始值
- 重新赋值 slice(如
s = append(s, ...))可能会创建新的底层数组,需要返回新的 slice 引用 - map 的键值修改 永远反映在原始 map 上
4. 结构体传递
结构体可以通过值传递或指针传递。值传递会复制整个结构体,而指针传递只复制指针(8或16字节)。
结构体值传递示例
go
package main
import "fmt"
type Person struct {
Name string
Age int
}
func setName(p Person) {
p.Name = "Changed" // 仅修改副本,原始保持不变
}
func main() {
p := Person{Name: "Alice"}
setName(p)
fmt.Println("姓名:", p.Name) // 输出: 姓名: Alice,未被修改
}结构体指针传递示例
go
package main
import "fmt"
type Person struct {
Name string
Age int
}
func setNamePtr(p *Person) {
p.Name = "Changed" // 修改原始结构体
}
func main() {
p := Person{Name: "Alice"}
setNamePtr(&p)
fmt.Println("姓名:", p.Name) // 输出: 姓名: Changed,已被修改
}选择建议
- 小结构体(如包含 1-2 个 int/string 字段)可以使用值传递,避免内存拷贝开销
- 大结构体或需要在函数内修改结构体成员时,使用指针传递
- Go 标准库中许多函数接受指针
5. 推荐最佳实践
- 对于基本类型(int, float, bool, string):使用值传递,默认即可
- 对于大类型或需要修改:使用指针传递
*Type - 对于 slice 和 map:
- 如果只需要修改内容(不重新分配),直接传递,修改会生效
- 如果需要重新分配或返回,返回新的 slice/map
- 文档注释:在函数签名中清楚说明参数是值还是指针,帮助调用者理解行为
- 一致性:在同一类型的相关函数中保持一致的传递方式
拿不准用哪种时看两点:要不要在函数内改原值,结构体大不大。要改或很大就用指针,其余传值即可。