Skip to content
  • 对于基本类型(如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  │  // 指针类型,地址相同
└──────┘        └──────┘
    ↓                ↓
┌──────┐
│ 数据区 │           // 共享同一数据
└──────┘

总结要点

  1. 字面意思:所有参数传递都是拷贝值
  2. 对于值类型:拷贝整个值,完全独立
  3. 对于引用类型:拷贝引用(地址),共享底层数据
  4. 关键区别:你可以通过副本地址修改原数据,但不能让调用者的引用指向新对象

特殊情况:slice 的内部结构

slice 其实是一个包含三个字段的结构体:

go
type slice struct {
    array unsafe.Pointer  // 底层数组指针
    len   int             // 长度
    cap   int             // 容量
}

传递 slice 时,拷贝的是这个结构体,所以能修改元素,但修改 len/cap 不会影响原 slice。

理解这个机制有助于:

  • 避免意外的数据修改
  • 理解性能影响(大结构体传指针更高效)
  • 正确设计函数接口
/src/technology/dateblog/2026/01/20260120-%E7%90%86%E8%A7%A3go%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E5%87%BD%E6%95%B0%E4%BC%A0%E5%8F%82%E9%83%BD%E6%98%AF%E5%80%BC%E6%8B%B7%E8%B4%9D.html