Go函数参数默认值传递,基本类型和无指针字段struct完全隔离;slice、map等仅复制header但共享底层数据,需显式拷贝避免意外修改。

Go语言中函数参数默认是值传递,这意味着传入函数的是原始数据的副本,函数内部对参数的修改不会影响原始变量。只要传入的是基本类型(如 int、string、struct)或其组合,且不涉及指针、切片、map、channel、interface 等引用类型底层结构,就能天然避免修改原数据。
理解Go的“值传递”本质
Go没有真正的“引用传递”,所有参数都是值传递——但关键在于:这个“值”本身可能是地址(如 slice 的 header、map 的 hmap 指针)。所以真正安全的值类型参数,是指那些在内存中完整复制、不共享底层数据的类型。
-
安全(完全隔离):
int、float64、bool、string(注意:string 是只读字节序列+长度,底层数据不可变)、小 struct(不含指针或引用字段) - 看似值传、实则共享底层:slice、map、channel、func、interface —— 它们的头部(header)被复制,但指向的底层数组/哈希表等仍是同一份
-
明确共享:指针(
*T)、带指针字段的 struct —— 复制的是地址,修改会反映到原数据
用 struct 实现纯值语义的数据封装
若需传递一组相关字段并确保不可被意外修改,推荐定义普通 struct(无指针字段),它会在调用时整体拷贝:
type User struct {
ID int
Name string
Age int
}
func updateUser(u User) User { // u 是副本
u.Age++ // 只改副本
u.Name = "New" // string 赋值也是新副本(因 string 不可变)
return u
}
u1 := User{ID: 1, Name: "Alice", Age: 25}
u2 := updateUser(u1)
// u1 保持不变:{1 "Alice" 25}
// u2 是新值:{1 "New" 26}
警惕“伪值类型”:slice 和 map 的常见陷阱
即使你没用指针,slice 和 map 在函数内仍可修改底层数组或哈希表内容:
立即学习“go语言免费学习笔记(深入)”;
func badModify(s []int) {
if len(s) > 0 {
s[0] = 999 // 修改了原底层数组!
}
}
data := []int{1, 2, 3}
badModify(data)
// data 现在变成 [999, 2, 3] —— 原数据被改了
✅ 正确做法:如需只读或隔离,显式拷贝底层数组:
- slice:用
append([]T(nil), s...)或copy到新 slice - map:手动遍历赋值到新 map(Go 1.21+ 可用
maps.Clone)
函数设计建议:显式表达意图
让调用者一眼明白是否会影响原数据:
- 接收值类型参数 + 返回新值(函数式风格):适合转换、计算类操作
- 接收指针参数(
*T):明确表示“可能修改原数据”,符合 Go 社区惯例 - 文档注释写明行为,例如
// Parse returns a copy; input is not modified
不复杂但容易忽略:只要传的是纯值类型(尤其小 struct 和 string),就无需额外防护;重点防范的是 slice/map 这类“头值+底层数组”混合体。










