程序中操作大量同类型变量时,为了方便数据的存储与操作,常常需要借助容器的力量。GO语言标准库提供了常用的容器实现。如固定大小的数组,可以动态扩容的切片,双向列表以及key-value形式存储的字典等。下面介绍数组与切片的基本使用。
数组
数组是一段存储固定长度的连续内存空间,它的大小在声明时就已经固定。虽然大小不能变化,但数组的成员却能修改。数组的声明样式如下:
var name [size]Type
数组大小必须指定,可以是常量或者表达式,必须在静态编译时确定其大小,不能动态指定,Type表示数组成员类型,可为任意类型。
数组的初始化可以在声明时使用初始化列表对数组进行初始化,也可以通过下标对数据成员进行访问和赋值,如下所示。
func main() {
var animals1 [3]string
animals1[0] = "cat"
animals1[1] = "dog"
animals1[2] = "rabbit"
fmt.Println(animals1) //[cat dog rabbit]
animals2 := [...]string{"duck", "chicken", "cat"}
fmt.Println(animals2) //[duck chicken cat]
}
使用初始化列表初始化数组时,需要注意[]内大小需要和{}内的数组成员的数量一致,上面的例子使用了...
让编译器根据{}的成员的数量确定数组的大小。
animals3 := new([3]string)
animals3[0] = "dog"
animals3[1] = "cat"
animals3[2] = "duck"
fmt.Println(*animals3) // [dog cat duck]
切片
切片是对数组一个连续片段的引用,它是一个容量可变的序列。它的内部结构包括底层数组指针、大小和容量。它通过指针引用底层数组,把对数据的读写限定在指定区域内。
切片的生成方式
1.从原生数组中生成切片并修改切片成员
从原有数组中生成一个切片,那么生成的切片指针即指向原始数组,格式如下:
slice := source[begin:end]
source表示生成切片的原有数组,begin表示切片的开始位置,end表示切片的结束位置(不包含结束位),这与python中语法是一致的。示例如下:
a := [...] int {0,1,2,3,4,5,6}
aSilce := a[0:2]
fmt.Printf("aSilce value is %v\naSilce len is %v\naSilce cap is %v\n", aSilce, len(aSilce), cap(aSilce))
// 输出如下:
aSilce value is [0 1]
aSilce len is 2
aSilce cap is 7
在这个切片中,我们仅能访问长度内的值,如果访问下标超过了切片的长度,编译器会抛出下标越界的异常。我们如果对切片内的成员进行修改,因为切片作用于原数组的引用,因此原数组值会进行改变。
aSilce[0] = 4
fmt.Printf("aSlice value is %v\nSource array value is %v", aSilce,a)
// aSlice value is [4 1]
// Source array value is [4 1 2 3 4 5 6]
上面的说法其实是有一定的问题的,如果当前切片容量可以容纳更多元素,即size小于cap,添加操作指向原有数组进行修改,当切片容量不足容纳更多元素,会申请新的地址空间,后面进行演示。
2.动态创建切片
可以通过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函数创建的新切片中的成员都被初始化为类型的初始值。
3.声明新的切片
直接声明切片类似与数组的初始化,但是不需要指定其大小,否则就变成了数组,样式如下:
var name []T
此时声明的切片没有分配内存,我们可以在声明切片的同时对其进行初始化,示例如下:
sli := [] int{1, 2, 3}
fmt.Printf("sli value is %v,len is %v,cap is %v",sli, len(sli),cap(sli))
//输出如下:
sli value is [1 2 3],len is 3,cap is 3
4.向切片中添加元素
GO语言提供了append内置函数用于动态向切片中添加元素,它将返回新的切片。
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)
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)
}
输出如下:
sli1 pointer is 0xc00000c060, len is 2, cap is 4, value is [1 2]
sli2 pointer is 0xc00000c080, len is 2, cap is 2, value is [3 4]
newSli1 pointer is 0xc00000c0e0, len is 3, cap is 4, value is [1 2 5]
source arr1 become [1 2 5 4]
newSli2 pointer is 0xc00000c120, len is 3, cap is 4, value is [3 4 5]
source arr2 become [1 2 3 4]
sli3 pointer is 0xc00000c160, len is 2, cap is 2, value is [1 2]
newSli3 pointer is 0xc00000c1a0, len is 3, cap is 4, value is [1 2 5]
source arr3 become [1 2 3 4]
我们发现容量足够的sli直接将append添加的新元素覆盖到原有的数组arr1中,而容量不足的sli2进行了扩容操作,申请了新的底层数组,不在原有的数组上进行操作了,实际使用中要特别留意。