Golang 中以标准库的方式提供了常用的容器实现,基本能够满足我们日常开发的需要。我们来具体学习下 Go 数组的使用。
切片是对数组一个连续片段的引用,它是一个容量可变的序列。我们可以简单将切片理解为动态数组,它的内部结构包括底层数组指针、大小和容量,它通过指针引用底层数组,把对数据的读写操作限定在指定的区域内。
切片的结构体由三部分组成:
array
是指向底层存储数据数组的指针;len
指当前切片的长度,即成员数量;cap
指当前切片的容量,它总是大于等于 len
。我们可以从原有数组中生成一个切片,那么生成的切片指针即指向原数组,生成的样式如下:
slice := source[begin:end]
source
表示生成切皮的原有数组,begin
表示切片的开始位置,end
表示切片的结束位置,不包含 end
索引指向的成员。具体效果如下例子所示:
source := [...]int{1,2,3}
sli := source[0:1]
fmt.Printf("sli value is %v\n", sli)
fmt.Printf("sli len is %v\n", len(sli))
fmt.Printf("sli cap is %v\n", cap(sli))
输出的结果为:
sli value is [1]
sli len is 1
sli cap is 3
在这个切片内,我们仅能访问长度内的值,如果访问的下标超过了切片的长度,编译器将会抛出下标越界的异常。如果此时我们对切片内的成员进行修改,因为切片作为指向原有数组的引用,对切片进行修改就是对原有数组进行修改,如下例子所示:
sli[0] = 4
fmt.Printf("sli value is %v\n", sli)
fmt.Printf("source value is %v\n", source)
结果如下所示:
sli value is [4]
source value is [4 2 3]
上面例子中我们修改了切片中的值,直接导致原数组中的值也发生了变化。
我们也可以通过 make
函数动态创建切片,在创建过程中指定切片的长度和容量,样式如下所示:
make([]T, size, cap)
T
即切片中的成员类型,size
为当前切片具备的长度,cap
为当前切片预分配的长度,即切片的容量。例子如下所示:
sli = make([]int, 2, 4)
fmt.Printf("sli value is %v\n", sli)
fmt.Printf("sli len is %v\n", len(sli))
fmt.Printf("sli cap is %v\n", cap(sli))
输出的结果如下:
sli value is [0 0]
sli len is 2
sli cap is 4
从上述输出可以看到 make
函数创建的新切片中的成员都被初始化为类型的初始值。
除了上面介绍的切片初始化方式。还可以直接声明新的切片,这就类似于数组的初始化,但是不需要指定其大小,否则就变成了数组,样式如下所示:
var name []T
此时声明的切片并没有分配内存,我们可以在声明切片的同时对其进行初始化,如下例子所示:
ex := []int{1,2,3}
fmt.Printf("ex value is %v\n", ex)
fmt.Printf("ex len is %v\n", len(ex))
fmt.Printf("ex len is %v\n", cap(ex))
结果如下所示:
ex value is [1 2 3]
ex len is 3
ex cap is 3
此时声明的切片大小和容量都为 3。
Golang 中提供了 append
内建函数用于动态向切片添加成员,它将返回新的切片。如果当前切片的容量可以容纳更多的成员,添加的操作将在切片指向的原有数组上进行,这将会覆盖掉原有数组的值;如果当前切片的容量不足以容纳更多的成员,那么切片将会进行扩容,具体的扩容过程为:申请一个新的连续内存空间,空间大小一般是原有容量的 2 倍,然后将原来数组中的数据拷贝到新的数组中,同时将切片中的指针指向新的数组,最后将新的成员添加到新的数组中。我们将通过下面的例子来进行演示:
package main
import "fmt"
func main() {
arr1 := [...]int{1,2,3,4}
arr2 := [...]int{1,2,3,4}
sli1 := arr1[0:2] // 长度为 2,容量为 4
sli2 := arr2[2:4] // 长度为 2,容量为 2
fmt.Printf("sli1 pointer is %p, len is %v, cap is %v, value is %v\n", &sli1, len(sli1), cap(sli1), sli1)
fmt.Printf("sli2 pointer is %p, len is %v, cap is %v, value is %v\n", &sli2, len(sli2), cap(sli2), sli2)
newSli1 := append(sli1, 5)
fmt.Printf("newSli1 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli1, len(newSli1), cap(newSli1), newSli1)
fmt.Printf("source arr1 become %v\n", arr1)
newSli2 := append(sli2, 5)
fmt.Printf("newSli2 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli2, len(newSli2), cap(newSli2), newSli2)
fmt.Printf("source arr2 become %v\n", arr2)
}
上述例子的结果为:
sli1 pointer is 0xc00000c040, len is 2, cap is 4, value is [1 2]
sli2 pointer is 0xc00000c060, len is 2, cap is 2, value is [3 4]
newSli1 pointer is 0xc00007c020, len is 3, cap is 4, value is [1 2 5]
source arr1 become [1 2 5 4]
newSli2 pointer is 0xc00007c060, len is 3, cap is 4, value is [3 4 5]
source arr2 become [1 2 3 4]
通过上面的例子,我们可以发现,容量足够的 sli1
直接将 append
添加的新成员覆盖到原有数组 arr1
,而容量不够的 sli2
进行了扩容操作,申请了新的底层数组,不在原数组的基础上进行操作。在实际使用的过程中要千万记住这两种区别。
如果原有数组可以添加新的成员,即切片指向的数组后还有空间,但切片的容量已经饱和,此时进行 append
操作,同样会进行扩容,申请新的内存空间,如下例子所示:
arr3 := [...]int{1,2,3,4}
sli3 := arr3[0:2:2] // 长度为 2,容量为 2
fmt.Printf("sli3 pointer is %p, len is %v, cap is %v, value is %v\n", &sli3, len(sli3), cap(sli3), sli3)
newSli3 := append(sli3,5)
fmt.Printf("newSli3 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli3, len(newSli3), cap(newSli3), newSli3)
fmt.Printf("source arr3 become %v\n", arr3)
对应的输出结果为:
sli3 pointer is 0xc00008e080, len is 2, cap is 2, value is [1 2]
newSli3 pointer is 0xc00006e0a0, len is 3, cap is 4, value is [1 2 5]
source arr3 become [1 2 3 4]
在上述代码中,我们指定了创建切片的第三个参数 cap
,cap
必须大于 end
,生成切片的容量将为 cap - begin
,限定了 sli3
的容量为 2。当进行 append
操作时,即使原有数组存在足够的空间,newSli3
还是指向新的数组空间。
为了方便切片的数据快速复制到另一个切片中,Golang 提供了内建的 copy
函数,它的使用样式如下:
copy(destSli, srcSli []T)
它的返回结果为实际发生复制的元素个数。如果要保证来源切片的数据都拷贝到目标切片,需要保证目标切片的长度大于等于来源切片的长度,否则将按照目标切面的长度大小进行拷贝。
本文主要介绍了切片的基本使用,切片本质就是一个结构体,他里面包含三部分:address + len + cap,因此作为一个引用空间,该空间和元素空间完全是两个空间,所以切片的首地址和头号元素的首地址完全不同。
总得来说 Go 语言中数组和切片有如下的区别:
切片是 Go 中提供了一种灵活,功能强悍的内置类型("动态数组")。与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。