- 对于基本类型(如int、float、bool、string等)和结构体(struct):传递的是值的副本,函数内部修改的是副本,不会影响原变量。
- 对于引用类型(如切片、映射、通道):它们本身是指向底层数据结构的指针(或包含指针的结构),传递的是这个指针的副本。因此,在函数内部通过这个副本指针修改底层数据,会影响到原始变量。但是,如果尝试修改指针本身(比如让它指向另一个地址),则不会影响原指针。
- 对于指针类型:传递的是指针的副本,即复制了一个指针,这个副本指针和原指针指向同一个地址。因此,通过副本指针修改目标值会影响原指针指向的值。但是,如果修改指针副本本身(如改变指向),则不会影响原指针。
核心概念:都是值拷贝
"值拷贝" 意味着函数调用时,会将参数的值复制一份传递给函数,而不是传递原始变量本身。
三种情况的示例
1. 基本类型(int, float, bool, string 等)
go
func modifyInt(x int) {
x = 100 // 修改的是副本
}
func main() {
a := 10
modifyInt(a)
fmt.Println(a) // 输出 10,原值不变
}2. 结构体(struct)
go
type Person struct {
Name string
Age int
}
func modifyStruct(p Person) {
p.Name = "Bob" // 修改的是副本
}
func main() {
person := Person{Name: "Alice", Age: 25}
modifyStruct(person)
fmt.Println(person.Name) // 输出 "Alice",原值不变
}3. 引用类型(slice, map, channel, pointer, function)
这是容易误解的地方:
go
func modifySlice(s []int) {
s[0] = 100 // 能修改原数据!
}
func main() {
slice := []int{1, 2, 3}
modifySlice(slice)
fmt.Println(slice) // 输出 [100 2 3]
}为什么说 "都是值拷贝"?
对于引用类型:
- 传递的是 引用的值(指针的值),而不是引用本身
- 可以理解为:传递的是 "地址的副本"
- 通过这个副本地址,可以访问和修改原始数据
go
func modifyPointer(p *int) {
*p = 100 // 通过地址副本修改原数据
}
func main() {
x := 10
ptr := &x
modifyPointer(ptr)
fmt.Println(x) // 输出 100
}重要区别:修改内容 vs 修改引用
go
func modifySliceContent(s []int) {
s[0] = 100 // 修改内容 - 会影响原数据
}
func modifySliceReference(s []int) {
s = append(s, 4) // 修改引用 - 不会影响原数据
// s 现在是新的切片头(新的地址)
}
func main() {
slice := []int{1, 2, 3}
modifySliceContent(slice)
fmt.Println(slice) // [100 2 3]
modifySliceReference(slice)
fmt.Println(slice) // [100 2 3] 而不是 [100 2 3 4]
}内存图示理解
原始变量 函数参数(副本)
┌──────┐ ┌──────┐
│ 10 │ │ 10 │ // int 类型,完全独立
└──────┘ └──────┘
┌──────┐ ┌──────┐
│ 0x1234 │ │ 0x1234 │ // 指针类型,地址相同
└──────┘ └──────┘
↓ ↓
┌──────┐
│ 数据区 │ // 共享同一数据
└──────┘总结要点
- 字面意思:所有参数传递都是拷贝值
- 对于值类型:拷贝整个值,完全独立
- 对于引用类型:拷贝引用(地址),共享底层数据
- 关键区别:你可以通过副本地址修改原数据,但不能让调用者的引用指向新对象
特殊情况:slice 的内部结构
slice 其实是一个包含三个字段的结构体:
go
type slice struct {
array unsafe.Pointer // 底层数组指针
len int // 长度
cap int // 容量
}传递 slice 时,拷贝的是这个结构体,所以能修改元素,但修改 len/cap 不会影响原 slice。
理解这个机制有助于:
- 避免意外的数据修改
- 理解性能影响(大结构体传指针更高效)
- 正确设计函数接口