我们都知道Go中slice切片是不同于数组的数据类型,他的亮点在在于它是引用类型,那么它是如何实现引用的呢?

首先我们先来明确一下他的结构,一个slice是包含 3个域的结构体,分别是:指向slice中的第一个元素的指针,slice的长度,以及slice的容量。区分一下长度和容量,长度是下表操作的上界,如x[i]中的i必须小于长度;容量是分割操作的上界,如x[i:j]中j不能大于容量。

定义一个切片时,并不会复制一份数据,而是会创建一个新的数据结构,像上面写的那样,指针指向的是同一个底层数组,举个例子:


a:=[]int{1,2,3,4,5}
b :=a[1:3]


a的切片里有五个元素,定义一个slice b截取a的1,2位置的值(注意在切片操作时,x[i:j]是不包括下标为j的值得),所以b中的结构中第一个元素就是一个指向底层数组下标为1元素的指针,但是在新的切片中索引不收原来数组的影响,即b[0]=2;

既然是引用类型,那么就说明修改b或a中任意一个相同的值,都会发生改变,让我们验证一下


a:=[]int{1,2,3,4,5}
b :=a[1:3]
b[0]=6
fmt.Println(a)
fmt.Println(b)


结果:

[1 6 3 4 5]
[6 3]
slice的扩容:

在对slice进行append等操作时,可能会造成slice的自动扩容。其扩容时的大小增长规则是:

  • 如果新的大小是当前大小2倍以上,则大小增长为新大小
  • 否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。

我们用例子说明一下:


a:=[]int{1,2,3,4,5}
b :=a[1:3]
fmt.Println(cap(b))
b=append(b,4,5)
fmt.Println(cap(b))


结果:

4
4
一开始b的容量为4,这与原来的数组容量有关系,a中一共有五个元素,默认容量为5,b截取的时下标1开始的,所以默认容量为4,这时候b中有两个元素,再增加两个,没超出,但是如果我们在增加一个呢?


a:=[]int{1,2,3,4,5}
b :=a[1:3]
fmt.Println(cap(a))
fmt.Println(cap(b))
b=append(b,4,5)
fmt.Println(cap(b))
b=append(b,7)
fmt.Println(cap(b))


结果:

5
4
4
8
很明显容量增加了一倍;修改一下,验证一下上面第一点


a:=[]int{1,2,3,4,5}
b :=a[1:3]
fmt.Println(cap(a))
fmt.Println(cap(b))
b=append(b,4,5,6,7,8,9,10)
fmt.Println(cap(b))


有上面,我们知道b的容量为4,所以增加七个元素后,一共有9个元素,变成了容量的2倍以上,我们看一下结果:

go语言中三个点 go语言slice_引用类型

容量变成了10,这里长度时9,这种情况下扩容后比实际具有的总长度还要大一些。

再来说一下定义一个切片的时候:可以用new和make,有什么区别呢?

首先一个最基本的区别就是:new()返回的是一个指针

我们先来介绍一下new(),他一般用于引用型变量定义的时候,给你定义的类型分配一块内存,然后返回一个执行这个内存的地址,他只需要一个参数,就是type,同时请注意它同时把分配的内存置为零,也就是类型的零值。

我们举个例子感受一下:

go语言中三个点 go语言slice_go语言中三个点_02

当我们定义一个指针,如果没有new,直接赋值的话就会出现错误,*a就是他的值,a就是一个地址。好处就是,利用new不用进行初始化

make:

make也是用于内存分配的,但是和new不同,它只用于chan、map以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。

看一下他的函数声明:

func make(t Type, size ...IntegerType) Type


很明显他要进行初始化,参数增加,并且返回的时他本身的类型