跳转到内容

Go 值传递与引用传递

来自代码酷

Go值传递与引用传递[编辑 | 编辑源代码]

介绍[编辑 | 编辑源代码]

在Go语言中,理解值传递引用传递的机制对编写高效、正确的程序至关重要。这两种传递方式决定了函数调用时参数的行为,直接影响内存管理和性能优化。

  • 值传递:函数接收参数的副本,修改副本不会影响原始数据。
  • 引用传递:函数接收参数的引用(如指针、切片、映射等),修改引用会影响原始数据。

Go语言中所有函数的参数传递默认是值传递,但通过指针、切片、映射等类型可以实现类似引用传递的效果。

值传递详解[编辑 | 编辑源代码]

当基本类型(如int、float、bool、string、数组、结构体)作为参数传递时,Go会复制其值到函数内。

代码示例[编辑 | 编辑源代码]

package main

import "fmt"

func modifyValue(x int) {
    x = 100
    fmt.Println("Inside modifyValue:", x)
}

func main() {
    num := 42
    fmt.Println("Before modifyValue:", num)
    modifyValue(num)
    fmt.Println("After modifyValue:", num)
}

输出:

Before modifyValue: 42
Inside modifyValue: 100
After modifyValue: 42

解释: 变量`num`的值被复制到函数`modifyValue`中,函数内修改的是副本,原始值不变。

引用传递的实现[编辑 | 编辑源代码]

Go通过指针(`*T`)或引用类型(切片、映射、通道)实现类似引用传递的效果。

指针示例[编辑 | 编辑源代码]

package main

import "fmt"

func modifyPointer(x *int) {
    *x = 100
    fmt.Println("Inside modifyPointer:", *x)
}

func main() {
    num := 42
    fmt.Println("Before modifyPointer:", num)
    modifyPointer(&num)
    fmt.Println("After modifyPointer:", num)
}

输出:

Before modifyPointer: 42
Inside modifyPointer: 100
After modifyPointer: 100

解释: 通过传递`num`的指针,函数内修改直接影响原始数据。

引用类型示例[编辑 | 编辑源代码]

切片、映射、通道本身是引用类型,传递时复制的是其底层结构的引用。

package main

import "fmt"

func modifySlice(s []int) {
    s[0] = 99
    fmt.Println("Inside modifySlice:", s)
}

func main() {
    slice := []int{1, 2, 3}
    fmt.Println("Before modifySlice:", slice)
    modifySlice(slice)
    fmt.Println("After modifySlice:", slice)
}

输出:

Before modifySlice: [1 2 3]
Inside modifySlice: [99 2 3]
After modifySlice: [99 2 3]

解释: 切片传递时复制的是其底层数组的指针,因此修改会影响原始数据。

内存模型分析[编辑 | 编辑源代码]

graph LR A[原始变量 num=42] -->|值传递| B[副本 x=42] C[原始指针 &num] -->|指针传递| D[副本 x=*num] E[切片 slice] -->|引用传递| F[副本 s=slice底层指针]

  • 值传递:内存中创建新副本。
  • 指针/引用传递:共享同一块内存地址。

实际应用场景[编辑 | 编辑源代码]

1. 性能优化:大结构体使用指针传递避免复制开销。 2. 数据共享:多个函数需修改同一数据时使用指针。 3. 并发安全:值传递可避免竞态条件,引用传递需加锁。

案例:结构体指针[编辑 | 编辑源代码]

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func updateAge(p *Person, newAge int) {
    p.Age = newAge
}

func main() {
    person := Person{"Alice", 30}
    fmt.Println("Before update:", person)
    updateAge(&person, 31)
    fmt.Println("After update:", person)
}

输出:

Before update: {Alice 30}
After update: {Alice 31}

常见误区[编辑 | 编辑源代码]

  • 误以为切片是值传递:切片本身是值传递,但其底层数组是共享的。
  • 混淆指针与引用:Go没有真正的引用传递,只有通过指针实现的间接引用。

数学表达[编辑 | 编辑源代码]

对于值传递,函数内的变量与原变量的关系: x副本=x原始(独立内存)

对于指针传递: *x指针=x原始(共享内存)

总结[编辑 | 编辑源代码]

  • 基本类型、数组、结构体默认值传递。
  • 指针、切片、映射、通道实现类似引用传递的效果。
  • 根据场景选择传递方式,平衡性能与安全性。