PHP中高级工程师面试重点讲解视频课程
Go快速入门浅显易懂视频教程-基础篇
Go快速入门浅显易懂视频教程-中级篇
go语言中一个典型的引用类型的数据使用案例的注意点-日常实战总结no.5
阅读:364 分享次数:0

首先我们来讲一些概念理论:按值传递和按引用传递,引用类型。

值传递:官方定义:指在调用函数时将实际参数复制一份传递到函数中。实际运用中解释:变量通过值传递之后,变量在函数里面里面的值发生变化之后,在函数外再使用此变量是传进来的值。

引用传递:官方定义:所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。实际运用中解释:变量通过引用传递之后,变量在函数里面的值可以发生变化,但是地址是和传进来之前的地址一样,最后在函数外再使用此变量会受函数里面值变化而变化。

引用类型:由类型的实际值引用(类似于指针)表示的数据类型。
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语言中我们可能经常会遇到此类问题。

浅度拷贝:直接赋值,拷贝的只是原始对象的引用地址,在堆中仍然共用一块内存。

深度拷贝:为新对象在堆中重新分配一块内存,所以对新对象的操作不会影响原始对象。


感觉本站内容不错,读后有收获?