4.1 数组的内部实现和基础功能
4.1.1 内部实现
数组是一个长度固定的数据类型。
4.1.2 声明和初始化
一旦声明,数组里存储的数据类型的数组长度就都不能改变了。
//声明一个包含5个元素的整型数组
var array [5]int
//声明一个包含5个元素的整型数组, 用初值初始化每个元素
array := [5]int{10,20,30,40,50}
在Go语言中声明变量时,总会使用对应类型的零值来对变量进行初始化。数组也不例外。
如果使用…替代数组的长度,Go语言会根据初始化时数组元素的数量来确定该数组的长度。
array := [...]int{10,20,30,40,50}
也可以指定特定元素的值
array := [5]int{1: 10, 2: 20}
//声明及初始化后,array中的值如下:
[0 10 20 0 0]
4.1.3 使用数组
同样类型的数组可以赋值给另一个数组。
var array1 [5]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
//把array2复制到array1,复制之后,两个数组的值完全一样。
array1 = array2
//修改array1不影响array2
array1[2] = "Black"
//修改之后
array1: [Red Blue Black Yellow Pink]
array2: [Red Blue Green Yellow Pink]
数组变量的类型包括数组长度和每个元素的类型。只有这两部分都相同的数组,才是类型相同的数组,才能互相赋值。
4.1.4 多维数组
数组本身只有一个维度,不过可以组合多个数组创建多维数组。
//声明并初始化外层数组中索引为1和3的元素
array := [4][2]int{1: {20, 26}, 3: {16, 18}}
//二维数组值如下:
[[0 0] [20 26] [0 0] [16 18]]
可以独立复制多维数组的某个维度
array := [4][2]int{1: {20, 26}, 3: {16, 18}}
var copyArray [2]int
copyArray = array[1]
//copyArray的值为
[20 26]
4.1.5 在函数间传递数组
在函数之间传递变量时,总是以值的方式传递。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制,并传递给函数。
好的方式是只传入指向数组的指针。
func foo(array *[1000000]int) {
... ...
}
更好的处理这类问题的方式是使用切片
4.2 切片的内部实现和基础功能
切片是围绕动态数组的概念构建的。
4.2.1 内部实现
切片是有3个字段的数据结构。这3个字段分别是指向底层数组的指针、切片访问的元素个数(即长度)和切片允许增长到的元素个数(即容量)。
4.4.2 创建和初始化
1. make和切片字面量
//只指定长度,切片的容量和长度相等
slice := make([]string, 3)
//也可以分别指定长度和容量
slice := make([]string, 3, 5)
//通过字面量创建切片
//长度的容量都是3
slice := []int{1,2,3}
//使用空字符串初始化第100个元素
slice1 := []string{99: "ball"}
2. nil和空切片
只要在声明时不做任何初始化,就会创建一个nil切片
// 长度容量均为0
var slice []int
//使用make创建空切片
slice := make([]int, 0)
//使用字面量创建空切片
slice := []int{}
4.2.3使用切片
1. 赋值和切片
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
slice和newSlice共享同一底层数组,但通过不同的切片会看到底层数组的不同部分。
对于newSlice,底层数组的容量只有4个元素。
需要记住的是,当两个切片共享一个底层数组时,如果一个切片修改了该底层数组的共享部分,另一个切片也能感知到。
与切片容量相关联的元素只能用于增长切片。在使用这部分元素前,必须将其合并到切片的长度量。
2. 切片增长
函数append总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
newSlice = append(newSlice, 60)
上例中newSlice在底层数组里还有额外的容量可用,append操作将可用的元素合并到切片的长度,并对其进行赋值。
如果切片的底层数组没有足够的可用容量,append函数会创建一个新的底层数据,将被引用的现有的值复制到新数组里,再追加新值。
函数append会智能地处理底层数组的容量增长。在切片容量小于1000个元素时,总是会成倍地增加容量。一旦元素个数超过100,容量的增长因子会设为1.25,也就是每次增加25%的容量。
3. 创建切片时的3个索引
如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个append操作创建底层数组,与原有的底层数组分离。
slice := []int{10, 20, 30, 40, 50}
//限制newSlice容量为1
newSlice := slice[1:2:2]
使用…运算符,可以将一个切片的所有元素追加到加一个切片。
s1 := []int{1, 2}
s2 := []int{3, 4}
s2 = append(s1, s2...)
//s2的值为
[1 2 3 4]
4.2.4 多维切片
//创建多维切片
slice := [][]int{{10}, {100, 200}}
创建之后slice的值看起来如下图展现的样子:
4.2.5 在函数间传递切片
func foo(slice []int)[]int {
...
return
}
slice := make([]int, 1000000)
foo(slice)
在64位机器上,一个切片需要24字节内存:8字节指针,8字节长度,8字节容量。
由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片复制到任意函数时,对底层数组大小都不会有影响。复制时只会复制切片本身。
4.3 映射的内部实现和基础功能
4.3.1 内部实现
即使使用同样的顺序保存键值对,每次迭代映射的时候顺序可能不一样。
映射是一个存储键值对的无序集合。
4.3.2 创建和初始化
可以使用make或映射字面量初始化映射。
映射的键可以是任何值,只要这个值可以使用==运算符做比较。切片、函数以及包含切片的结构类型由于具有引用语义,不能做为映射键。
4.3.3 使用映射
可以通过声明一个未初始化的映射来创建一个值为nil的映射。nil映射不能用于存储键值对。
//创建nil映射
var colors map[string]string
//获取并测试某个键是否存在
value, exists := colors["blue"]
if exists {
fmt.Println(value)
}
如果想把一个键值对从映射里删除,可以使用delete函数。
4.3.4 在函数间传递映射
在函数间传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改。
4.4 小结
- 数组是构造切片和映射的基石
- Go语言里切片经常用来处理数据的集合,映射用来处理具有键值对结构的数据。
- 内置函数make可以创建切片和映射,并指定原始的长度和容量。也可以直接使用切片和映射字面量,或者使用字面量作为变量的初始值。
- 切片有容量限制,不过可以使用内置的append函数扩展容量。
- 映射的增长没有容量或者任何限制。
- 内置函数len可以用来获取切片或者映射的长度。
- 内置函数cap只能用于切片
- 通过组合,可以创建多维数组和多维切片。也可以使用切片或者其也映射作为映射的值。但是切片不能用作映射的键
- 将切片或者映射传递给函数成本很小,并且不会复制底层的数据结构。