在 Go 语言中,值传递(Pass by Value)和引用传递(Pass by Reference)描述了函数调用时参数传递的方式,它们决定了函数内部对参数的修改是否会影响函数外部的变量。
1. 值传递(Pass by Value)
定义
值传递是指将实参的副本传递给函数。函数内部的参数是实参的副本,对参数的任何修改都不会影响到原始实参。
值传递的特点
-
在值传递中,函数接收到的是实参的副本。实参和形参是完全独立的内存空间,修改形参不会影响实参。
-
常见的值类型(如:基本数据类型、数组、结构体)是通过值传递的。
如何使用值传递
-
对于基本数据类型(如
int
、float
、string
)以及数组、结构体,默认使用值传递。 -
如果需要在函数内修改数据,并且不希望修改外部数据,可以使用值传递。
示例代码:值传递
package main import "fmt" // 值传递:传递的是变量的副本 func modifyValue(x int) { x = 100 // 修改副本 } func main() { a := 10 modifyValue(a) // a 的副本传递给函数 fmt.Println(a) // 输出 10,a 并没有被修改 }
输出
10
总结:
-
对
a
的修改只发生在modifyValue
函数内部,外部的a
保持不变。
2. 引用传递(Pass by Reference)
定义
引用传递是指将实参的地址(指针)传递给函数,函数内部的参数是实参的引用(指向相同的内存地址)。因此,函数内对参数的修改会直接影响原始实参。
引用传递的特点
-
在引用传递中,函数接收到的是实参的内存地址(即指向数据的指针)。通过这个指针,函数可以直接修改原始数据。
-
常见的引用类型(如:切片、映射、通道、指针、接口)是通过引用传递的。
-
即使是值类型,如果将其传递给指针,也会实现引用传递。
如何使用引用传递
-
对于引用类型,Go 会直接传递其内存地址。
-
对于值类型,如果希望传递引用(即原始数据的地址),可以将值类型的变量包装成指针类型并传递。
示例代码:引用传递
package main import "fmt" // 引用传递:传递的是指向原始数据的地址 func modifyReference(x *int) { *x = 100 // 通过指针修改原始数据 } func main() { a := 10 modifyReference(&a) // 传递 a 的地址给函数 fmt.Println(a) // 输出 100,a 被修改了 }
输出
100
总结:
-
通过指针传递,
modifyReference
函数修改了原始变量a
的值。
3. 值传递与引用传递的区别
特性 | 值传递 | 引用传递 |
---|---|---|
传递的内容 | 传递实参的副本 | 传递实参的地址(指针) |
内存分配 | 为形参分配新的内存空间 | 形参和实参共享相同的内存地址 |
函数内修改的影响 | 修改形参不会影响实参 | 修改形参会直接影响实参 |
常见类型 | 基本数据类型、数组、结构体 | 切片、映射、通道、指针、接口等引用类型 |
使用场景 | 需要保护原始数据不被修改时使用 | 需要修改原始数据或共享数据时使用 |
4. 如何选择使用值传递或引用传递
选择值传递的场景:
-
需要独立副本:如果你希望函数内部修改参数不会影响到外部的原始数据,使用值传递。
-
小数据量:对于较小的数据类型(如基本类型
int
、float64
等),值传递开销较小,性能开销不大。 -
不可变数据:如果数据应该保持不变,值传递是比较安全的选择。
选择引用传递的场景:
-
需要修改原始数据:如果你希望函数内部修改参数并且修改反映到外部,使用引用传递。
-
大型数据结构:对于大型数据结构(如
slice
、map
、struct
)或大数组,使用引用传递可以避免不必要的内存复制,提高性能。 -
共享数据:当多个函数或多个地方需要共享同一份数据时,引用传递是必要的。
5. Go 中值传递与引用传递的示例
示例 1:值传递
package main import "fmt" func updateValue(a int) { a = 20 // 只修改函数内的副本 } func main() { x := 10 updateValue(x) fmt.Println(x) // 输出 10,x 未被修改 }
示例 2:引用传递(使用指针)
package main import "fmt" func updateValue(a *int) { *a = 20 // 修改原始数据 } func main() { x := 10 updateValue(&x) // 传递 x 的指针 fmt.Println(x) // 输出 20,x 被修改 }
示例 3:引用类型(切片)
package main import "fmt" func updateSlice(s []int) { s[0] = 100 // 修改原始切片 } func main() { arr := []int{1, 2, 3} updateSlice(arr) fmt.Println(arr) // 输出 [100 2 3],arr 被修改 }
总结:
-
值传递:适用于需要独立副本或不可变数据的场景,函数修改参数不会影响外部数据。
-
引用传递:适用于共享数据或需要修改原始数据的场景,函数修改参数会直接影响原始数据。