首先我们来讲一些概念理论:按值传递和按引用传递,引用类型。
值传递:官方定义:指在调用函数时将实际参数复制一份传递到函数中。实际运用中解释:变量通过值传递之后,变量在函数里面里面的值发生变化之后,在函数外再使用此变量是传进来的值。
引用传递:官方定义:所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。实际运用中解释:变量通过引用传递之后,变量在函数里面的值可以发生变化,但是地址是和传进来之前的地址一样,最后在函数外再使用此变量会受函数里面值变化而变化。
引用类型:由类型的实际值引用(类似于指针)表示的数据类型。
go里面的指针、map、slice、chan是引用类型。
go里面的int、string、struct是非引用类型。
下面我们来讲两个例子来说明go语言中传值方式。
非引用类型例子:
package main
import "fmt"
func main() {
t1 := "aaa"
fmt.Println("函数执行开始前")
fmt.Println(t1)
fmt.Println(&t1)
aa(t1)
fmt.Println("函数执行结束后")
fmt.Println(t1)
fmt.Println(&t1)
}
func aa(t string) {
fmt.Println("函数执行中")
fmt.Println(t)
fmt.Println(&t)
}
返回结果,函数中的地址和传进去之前的地址是变化的,所以go的非引用类型是值传递。
函数执行开始前
aaa
0xc0000561c0
函数执行中
aaa
0xc0000561e0
函数执行结束后
aaa
0xc0000561c0
引用类型例子:
package main
import "fmt"
func main() {
t2 := map[string]int{
"aa": 1,
"bb": 2,
}
fmt.Println("函数执行开始前")
fmt.Println(t2)
fmt.Printf("%p\n", &t2)
bb(t2)
fmt.Println("函数执行结束后")
fmt.Println(t2)
fmt.Printf("%p\n", &t2)
}
func bb(t map[string]int) {
fmt.Println("函数执行中")
fmt.Println(t)
fmt.Printf(&t)
}
返回结果如下:我们可以看出go的引用类型也是值传递。
函数执行开始前
map[aa:1 bb:2]
0xc000006028
函数执行中
map[aa:1 bb:2]
0xc000006038
函数执行结束后
map[aa:1 bb:2]
0xc000006028
Go语言中所有的传参都是值传递,都是一个副本,一个拷贝。在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型。请记住,引用类型和传引用是两个概念。
下面我们来讲一下事例,可能我们了解这样的理论之后,在简单平常的使用中可能遇到的问题会比较少,但遇到复杂的逻辑之后就可能会遇到一些问题。
下面我们列下我实际业务中遇到的一个问题。
package main
import "fmt"
func main() {
//这里我讲一下map和slice的特殊例子使用,这个也是我在实际的应用中有遇到的问题,其实主要是因为map和slice是引用类型
//go本身的值传递,但是map是引用类型,所以会造成这样的问题
m := make(map[string]bool)
m["a"] = true
m["b"] = true
a, b := a1(m)
fmt.Println("最终输出结果1")
fmt.Println(a, b)
arr := make([]string, 0)
arr = append(arr, "aaa")
arr = append(arr, "bbb")
arr = append(arr, "ccc")
c, d := b1(arr)
fmt.Println("最终输出结果2")
fmt.Println(c, d)
}
func b1(a []string) ([]string, []string) {
b := make([]string, 0)
fmt.Println("输入之前---")
fmt.Println(a)
for k, v := range a {
if v == "aaa" {
b = append(b, "aaa1")
a[k] = "aaa1"
}
}
fmt.Println("第一次循环之后---")
fmt.Println(a)
c := make([]string, 0)
for k, v := range a {
if v == "ccc" {
c = append(c, "ccc1")
a[k] = "ccc1"
}
}
fmt.Println("第二次循环之后---")
fmt.Println(a)
return b, c
}
//这里的函数其实当时我的实际功能是,map传进去是不变的,然后我有第一次循环,第二次循环,两次循环想都能是传进去的值,返回不同的结果,我这里用不同的表现来表现当前的问题
func a1(a map[string]bool) (map[string]string, map[string]string) {
b := make(map[string]string)
fmt.Println("输入之前---")
fmt.Println(a)
for k, v := range a {
if k == "a" {
a[k] = false
}
if v == false {
b[k] = "1"
}
}
fmt.Println("第一次循环之后---")
fmt.Println(a)
c := make(map[string]string)
for k, v := range a {
if v {
c[k] = "1"
}
}
fmt.Println("第二次循环之后---")
fmt.Println(a)
return b, c
}
输出结果如下:
输入之前---
map[a:true b:true]
第一次循环之后---
map[a:false b:true]
第二次循环之后---
map[a:false b:true]
最终输出结果1
map[] map[b:1]
输入之前---
[aaa bbb ccc]
第一次循环之后---
[aaa1 bbb ccc]
第二次循环之后---
[aaa1 bbb ccc1]
最终输出结果2
[aaa1] [ccc1]
关于一个map的值,其实第一次循环我想用传进来的值,第二次循环还是想用我传进来的值,但是因为map是引用类型,造成第二次循环的值受第一次循环的影响,这个时候我们要解决此类问题,我们就需要使用深度复制来解决此类问题,在java语言中我们可能经常会遇到此类问题。
浅度拷贝:直接赋值,拷贝的只是原始对象的引用地址,在堆中仍然共用一块内存。
深度拷贝:为新对象在堆中重新分配一块内存,所以对新对象的操作不会影响原始对象。