值传递的核心思想是:当一个变量作为参数传递给函数时,函数接收到的是该变量的一个副本 (copy),而不是变量本身。 函数内部对这个副本的任何操作,都不会影响到函数外部的原始变量。
Go 中的基本类型(如 int
, float
, string
, bool
)、结构体 (struct
) 和数组 (array
) 都属于值类型。
当你将这些类型的变量传递给函数时,Go 会创建这些变量的一个完整副本,并将副本交给函数。
示例:
package main
import "fmt"
// 修改 int 副本
func modifyInt(val int) {
val = 100 // 这里修改的是传入参数 a 的副本
fmt.Println("Inside modifyInt:", val) // 输出 100
}
// 修改 struct 副本
func modifyStruct(p struct{ x int }) {
p.x = 200 // 这里修改的是传入参数 pt 的副本的 x 字段
fmt.Println("Inside modifyStruct:", p.x) // 输出 200
}
func main() {
// int 示例
a := 10
fmt.Println("Before modifyInt:", a) // 输出 10
modifyInt(a)
fmt.Println("After modifyInt:", a) // 输出 10,原始 a 未改变
fmt.Println("---")
// struct 示例
type Point struct {
x int
}
pt := Point{x: 20}
fmt.Println("Before modifyStruct:", pt.x) // 输出 20
modifyStruct(pt)
fmt.Println("After modifyStruct:", pt.x) // 输出 20,原始 pt 未改变
}
从输出可以看出,modifyInt
和 modifyStruct
函数内部对参数的修改,完全没有影响到 main
函数中的原始变量 a
和 pt
。
现在我们来看看指针 (pointer
)、切片 (slice
)、映射 (map
)、通道 (channel
) 和函数 (func
)。这些类型经常被误认为是“引用传递”,但它们本质上仍然是值传递。传递的是它们内部结构(通常包含一个指向底层数据的指针)的副本。
*T
)传递指针时,传递的是指针地址的副本。这个副本指向与原始指针相同的内存地址。因此,函数可以通过这个地址副本修改原始指针指向的数据,但不能修改原始指针变量本身(比如让它指向一个新的地址)。
示例:
package main
import "fmt"
func modifyPointer(ptr *int) {
*ptr = 100 // 通过地址副本,修改了原始变量 b 的值
fmt.Println("Inside modifyPointer (value pointed to):", *ptr) // 输出 100
// 尝试修改指针本身(让副本指向新的地址)
// x := 200
// ptr = &x // 这只会改变 ptr 这个副本,不会改变 main 函数中的 bptr
}
func main() {
b := 10
bptr := &b // bptr 存储 b 的地址
fmt.Println("Before modifyPointer:", b) // 输出 10
modifyPointer(bptr)
fmt.Println("After modifyPointer:", b) // 输出 100,原始 b 被修改
}
slice
)切片本身是一个小结构体,包含三个字段:
ptr
)len
)cap
)当你传递一个切片时,是把这个结构体进行了值拷贝。这意味着:
s[i] = newValue
),这个修改会反映到原始切片上,因为它们操作的是同一个底层数组。append
操作导致切片扩容(底层数组被重新分配),函数内的切片副本会指向新的底层数组,而外部的原始切片仍然指向旧数组。这时,函数内对新数组的修改不会影响外部。s = anotherSlice
),只会改变函数内副本的指向,不影响外部。示例:
package main
import "fmt"
func modifySlice(s []int) {
if len(s) > 0 {
s[0] = 99 // 修改共享底层数组的元素
}
fmt.Println("Inside modifySlice (first element):", s[0]) // 输出 99
// 尝试 append (如果未扩容,会影响外部;如果扩容,则不影响)
// s = append(s, 4)
// fmt.Println("Inside modifySlice (after append):", s)
}
func main() {
sl := []int{1, 2, 3}
fmt.Println("Before modifySlice:", sl) // 输出 [1 2 3]
modifySlice(sl)
fmt.Println("After modifySlice:", sl) // 输出 [99 2 3],原始切片元素被修改
}
map
) 和 通道 (channel
)映射和通道与切片类似,它们内部也包含一个指向底层数据结构的指针。
传递 map
或 channel
时,传递的是它们内部指针的副本。因此,在函数内部对 map
或 channel
进行的操作(如添加键值对、发送/接收数据)会影响到原始的 map
或 channel
,因为它们都指向同一个底层实现。同样,在函数内对 map
或 channel
变量重新赋值也不会影响外部。
示例 (Map):
package main
import "fmt"
func modifyMap(m map[string]int) {
m["b"] = 200 // 操作共享的底层数据结构
fmt.Println("Inside modifyMap:", m) // 输出 map[a:1 b:200]
// 尝试重新赋值 (只会改变副本 m)
// m = make(map[string]int)
// m["c"] = 300
}
func main() {
mp := map[string]int{"a": 1}
fmt.Println("Before modifyMap:", mp) // 输出 map[a:1]
modifyMap(mp)
fmt.Println("After modifyMap:", mp) // 输出 map[a:1 b:200],原始 map 被修改
}
Go 语言的参数传递机制始终是值传递。
int
, string
, struct
, array
等),传递的是值的完整副本,函数内部的修改不影响外部。记住:一切皆是值传递,只是传递的值有时是指针(或者包含指针的结构)的副本而已。
如果您喜欢我的文章,请点击下面按钮随意打赏,您的支持是我最大的动力。
最新评论