Go语言函数参数的传递
1、值传递(传值)
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到原内容数据。默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到原内容数据。
每次调用函数,都将实参复制一份再传递到函数中。每次都复制一份,性能会下降,但是Go语言中使用指针和值传递配合就避免了性能降低问题,也就是通过传指针参数来解决实参复制的问题。
2、引用传递(传引用)
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到原内容数据。严格来说Go语言只有值传递这一种传参方式,Go语言是没有引用传递的,但是Go语言中可以借助传指针来实现引用传递的效果。函数参数使用指针参数,传参的时候其实是复制一份指针参数,也就是复制了一份变量地址。
函数的参数如果是指针,当函数调用时,虽然参数仍然是按复制传递的,但是此时仅仅只是复制一个指针,也就是一个内存地址,这样就不用担心实参复制造成的内存浪费、时间开销、性能降低。
引用传递的作用如下:
- 传指针使得多个函数能操作同一个对象;
- 传指针更轻量级(8 bytes),只需要传内存地址。如果参数是非指针参数,那么值传递的过程中,每次在复制上面就会花费相对较多的系统开销(内存和时间)。所以要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中 slice、map、chan 类型的实现机制都类似指针,所以可以直接传递,而不必取地址后传递指针。
函数传 int 类型的值与引用的对比,如下所示。
package main
import "fmt"
func main(){
a := 10
fmt.Printf("1. 变量 a 的内存地址:%p, 值为:%v \n\n", &a, a)
fmt.Printf("========int 型变量 a 的内存地址:%p \n\n", a)
changeIntVal(a)
fmt.Printf("2. changeIntVal 函数调用之后:变量 a 的内存地址:%p, 值为:%v \n\n", &a, a)
changeIntPtr(&a)
fmt.Printf("3. changeIntPtr 函数调用之后:变量 a 的内存地址:%p, 值为:%v \n\n", &a, a)
}
func changeIntVal(a int){
fmt.Printf("-------changeIntVal 函数内:值参数 a 的内存地址:%p, 值为:%v \n", &a, a)
a = 90
}
func changeIntPtr(a *int){
fmt.Printf("-------changeIntPtr 函数内:指针参数 a 的内存地址:%p, 值为:%v \n", &a, a)
*a = 50
}
运行结果如下:
1. 变量 a 的内存地址:0xc00000e0a8, 值为:10
========int 型变量 a 的内存地址:%!p(int=10)
-------changeIntVal 函数内:值参数 a 的内存地址:0xc00000e0e0, 值为:10
2. changeIntVal 函数调用之后:变量 a 的内存地址:0xc00000e0a8, 值为:10
-------changeIntPtr 函数内:指针参数 a 的内存地址:0xc000006030, 值为:0xc00000e0a8
3. changeIntPtr 函数调用之后:变量 a 的内存地址:0xc00000e0a8, 值为:50
函数传 slice 类型的值与引用的对比,如下所示。
package main
import "fmt"
func main(){
a := []int{1, 2, 3, 4}
fmt.Printf("1. 变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a) //[1, 2, 3, 4]
fmt.Printf("切片型变量 a 内存地址是:%p \n\n", a) //可以获取到地址, 类似:0xc420018080
//传值
changeSliceVal(a)
fmt.Printf("2. changeSliceVal 函数调用后:变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
//传引用
changeSlicePtr(&a)
fmt.Printf("3. changeSlicePtr 函数调用后:变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
}
func changeSliceVal(a []int){
fmt.Printf("-------changeSliceVal 函数内:值参数 a 的内存地址是:%p, 值为:%v \n", &a, a)
fmt.Printf("-------changeSlicePtr 函数内:值参数 a 的内存地址是:%p \n", a)
a[0] = 99
}
func changeSlicePtr(a *[]int){
fmt.Printf("-------changeSlicePtr 函数内:指针参数 a 的内存地址是:%p, 值为:%v \n", &a, a)
(*a)[1] = 250
}
运行结果如下:
1. 变量 a 的内存地址是:0xc0000044a0, 值为:[1 2 3 4]
切片型变量 a 内存地址是:0xc00000c360
-------changeSliceVal 函数内:值参数 a 的内存地址是:0xc000004520, 值为:[1 2 3 4]
-------changeSlicePtr 函数内:值参数 a 的内存地址是:0xc00000c360
2. changeSliceVal 函数调用后:变量 a 的内存地址是:0xc0000044a0, 值为:[99 2 3 4]
-------changeSlicePtr 函数内:指针参数 a 的内存地址是:0xc000006030, 值为:&[99 2 3 4]
3. changeSlicePtr 函数调用后:变量 a 的内存地址是:0xc0000044a0, 值为:[99 250 3 4]
package main
import "fmt"
func main(){
a := [4]int{1, 2, 3, 4}
fmt.Printf("1. 变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
fmt.Printf("数组型变量 a 内存地址是:%p \n\n", a)
//传值
changeArrayVal(a)
fmt.Printf("2. changeArrayVal 函数调用后:变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
//传引用
changeArrayPtr(&a)
fmt.Printf("3. changeArrayPtr 函数调用后:变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
}
func changeArrayVal(a [4]int){
fmt.Printf("----------changeArrayVal 函数内:值参数 a 的内存地址是:%p, 值为:%v \n", &a, a)
fmt.Printf("----------changeArrayPtr 函数内:值参数 a 的内存地址是:%p \n", a) //获取不到地址
a[0] = 99
}
func changeArrayPtr(a *[4]int){
fmt.Printf("----------changeArrayPtr 函数内:指针参数 a 的内存地址是:%p, 值为:%v \n", &a, a)
(*a)[1] = 250
}
运行结果如下:
1. 变量 a 的内存地址是:0xc00006c120, 值为:[1 2 3 4]
数组型变量 a 内存地址是:%!p([4]int=[1 2 3 4])
----------changeArrayVal 函数内:值参数 a 的内存地址是:0xc00006c180, 值为:[1 2 3 4]
----------changeArrayPtr 函数内:值参数 a 的内存地址是:%!p([4]int=[1 2 3 4])
2. changeArrayVal 函数调用后:变量 a 的内存地址是:0xc00006c120, 值为:[1 2 3 4]
----------changeArrayPtr 函数内:指针参数 a 的内存地址是:0xc000098020, 值为:&[1 2 3 4]
3. changeArrayPtr 函数调用后:变量 a 的内存地址是:0xc00006c120, 值为:[1 250 3 4]
package main
import "fmt"
type Teacher struct{
name string
age int
married bool
sex int8
}
func main(){
a := Teacher{"Steven", 35, true, 1}
fmt.Printf("1. 变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a) //{Steven 35 true 1}
fmt.Printf("struct 型变量 a 内存地址是:%p \n\n", a) //可以获取到地址?
//传值
changeStructVal(a)
fmt.Printf("2. changeArrayVal 函数调用后:变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
//传引用
changeStructPtr(&a)
fmt.Printf("3. changeArrayPtr 函数调用后:变量 a 的内存地址是:%p, 值为:%v \n\n", &a, a)
}
func changeStructVal(a Teacher){
fmt.Printf("---------changeArrayVal 函数内:值参数 a 的内存地址是:%p, 值为:%v \n", &a, a)
fmt.Printf("---------changeArrayPtr 函数内:值参数 a 的内存地址是:%p \n", a) //获取不到地址?
a.name = "Josh"
a.age = 29
a.married = false
}
func changeStructPtr(a *Teacher){
fmt.Printf("-------changeArrayPtr 函数内:指针参数 a 的内存地址是:%p, 值为:%v \n", &a, a)
(*a).name = "Daniel"
(*a).age = 20
(*a).married = false
}
运行结果如下:
1. 变量 a 的内存地址是:0xc0000044a0, 值为:{Steven 35 true 1}
struct 型变量 a 内存地址是:%!p(main.Teacher={Steven 35 true 1})
---------changeArrayVal 函数内:值参数 a 的内存地址是:0xc000004520, 值为:{Steven 35 true 1}
---------changeArrayPtr 函数内:值参数 a 的内存地址是:%!p(main.Teacher={Steven 35 true 1})
2. changeArrayVal 函数调用后:变量 a 的内存地址是:0xc0000044a0, 值为:{Steven 35 true 1}
-------changeArrayPtr 函数内:指针参数 a 的内存地址是:0xc000006030, 值为:&{Steven 35 true 1}
3. changeArrayPtr 函数调用后:变量 a 的内存地址是:0xc0000044a0, 值为:{Daniel 20 false 1}
3、值传递和引用传递的细节问题
Go语言中所有的传参都是值传递(传值),都是一个副本。副本的内容有的是值类型(int、string、bool、array、struct 属于值类型),这样在函数中就无法修改原内容数据;有的是引用类型(pointer、slice、map、chan 属于引用类型),这样就可以修改原内容数据。是否可以修改原内容数据,和传值、传引用没有必然的关系。在 C++ 中,传引用肯定是可以修改原内容数据的;在Go语言里,虽然只有传值,但是也可以修改原内容数据,因为参数可以是引用类型。
传引用和引用类型是两个概念。虽然Go语言只有传值一种方式,但是可以通过传引用类型变量达到与传引用一样的效果。
声明:《Go系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
ICP备案:
公安联网备案: