MENU

Go的引用类型和基本类型的区别

April 25, 2022 • Golang

Go语言在2013年4月3日的时候删除了go引用类型相关的概念,以下内容请注意鉴别
参考链接:https://github.com/golang/go/commit/b34f0551387fcf043d65cd7d96a0214956578f94
参考链接:https://www.tapirgames.com/blog/golang-has-no-reference-values

序言

这篇文章其实相当基础,但是因为我在学习这门语言的时候,没有注意到他的相关特性,导致后面犯了一个好大的错误(痛失offer),然后决定来好好研究一下这个东西

go语言将数据类型分为四类,包括:基础类型,复合类型,引用类型和接口类型,其中

go语言的引用类型包括:指针切片字典函数通道

go语言的基本类型包括:数字字符串布尔数组接口类型

go语言的复合数据类型:数组结构体

数据类型参考自:《The Go Programming Language》——Alan A. A. Donovan , Brian W. Kernighan

基本类型:

基本类型在进行值传递的时候,默认是进行的值传递,是传递的值的副本,而不是这个值本身,我们可以大致参考一下下面这份代码:

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 11:39:26
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num int) {
  num = 123
  fmt.Println(num)  // 123
}

func main() {
  a := 12
  modify(a)
  fmt.Println(a)  // 12
}

我们可以看到,在modify函数中对传入的值进行修改,不会对原本的变量产生影响,特别的,我们再看看数组,golang中的数组也是传递的值,因此在数组的长度特别大的时候,为了让程序有更好的性能,我们应当优先选择传入切片而非数组,至于原因我会在后文叙述

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 11:43:26
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num [10]int) {
  num[0] = 12
  fmt.Println(num)  // 12, 2, 3, 4, 5, 6
}

func main() {
  a := [10]int{1, 2, 3, 4, 5, 6}
  modify(a)
  fmt.Println(a)  // 1, 2, 3, 4, 5, 6
}

值得注意的是,如果想要实现在函数内部修改函数外的变量,你应该这样做:

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 12:35:25
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num *int) {
  *num = 33
}

func main() {
  a := 22
  fmt.Println(a)
  modify(&a)
  fmt.Println(a)
}

对于所有的基本类型,上述的规则都是适用的,因此布尔型等其他的数据类型我将不进行赘述

引用类型:

我们先来看一段源码

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 12:10:53
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num []int) {
  num[0] = 12
  fmt.Println(num) // 12, 2, 3, 4, 5, 6
}

func main() {
  a := []int{1, 2, 3, 4, 5, 6}
  modify(a)
  fmt.Println(a) // 12, 2, 3, 4, 5, 6
}

很奇怪,我们在主函数里面并没有修改a的值,但是为什么a的值会发生改变呢?其实这是因为,a其实是一个切片(slice)类型,我们看一下slice的底层数据结构(源码):

type slice struct {
    array unsafe.Pointer    // 指向底层数组
    len int                 // slice 长度
    cap int                 // slice 底层数组容量|预分配的大小
}

我们可以看到,在slice中,有一个指向底层数组的指针,也就是说slice其实本身并没有存储值,而是存储了一个数组的指针,我们将切片传递到函数内部,实际上是在传递一个指针(是slice内部封装的指针,而非slice本身是一个指针),我们对它的修改都可以看作是对slice内部封装的指针的修改(其实两者还是有一些区别的)

我们来看一段代码片段,再来理解一下引用类型

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 12:22:06
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num []int) {
  num = []int{33, 44, 55, 66}
  fmt.Println(num) // 33, 44, 55, 66
}

func main() {
  a := []int{1, 2, 3, 4, 5, 6}
  modify(a)
  fmt.Println(a) // 1, 2, 3, 4, 5, 6
}

通过上述的代码,我相信你应该能明白我为什么说向函数传入的slice修改的是传入slice内部的指针了吧(传入slice同时也是具有一部分基本类型的特性的)

当然,还有一种append的情况,这里的append向num中添加新的item的时候,因为append发生了扩容,所以num内部的指针发生了改变,即此时的num已经是一个新的切片了,所以原来的a是不会发生改变的(上次面试就是在这里挂了,那个面试官连个情况都表述不清楚)

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 12:25:38
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num []int) {
  num = append(num, 7)
  fmt.Println(num) // 1, 2, 3, 4, 5, 6, 7
}

func main() {
  a := []int{1, 2, 3, 4, 5, 6}
  modify(a)
  fmt.Println(a)  // 1, 2, 3, 4, 5, 6
}

值得注意的是,使用interface也可能会导致值得的改变,这点比较坑,不过我认为智力正常的公司应该都不会允许员工写出这种样子的代码吧...

/*
 * @Author: NorthCity1984
 * @LastEditTime: 2022-04-25 11:49:20
 * @Description:
 * @Website: https://grimoire.cn
 * Copyright (c) NorthCity1984 All rights reserved.
 */
package main

import "fmt"

func modify(num interface{}) {
  num.([]int)[0] = 12
  fmt.Println(num) // 12, 2, 3, 4, 5, 6
}

func main() {
  a := []int{1, 2, 3, 4, 5, 6} // 12, 2, 3, 4, 5, 6
  modify(a)
  fmt.Println(a)
}

剩下的所有的引用类型都有与上面相似的原理,我这里同样就不加以赘述了

作者:NorthCity1984
出处:https://grimoire.cn/golang/reference-basic-types.html
版权:本文《Go的引用类型和基本类型的区别》版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

Last Modified: May 6, 2022