GO语言实战——第四章
- 数组、切片和映射
数组
- 声明和初始化
声明数组时需要指定内部存储的数据的类型,以及需要存储的元素的数量。
//声明一个五个元素的整数数组
var array [5]int
(注意:一旦声明,数组里存储的数据类型和数组长度就都不能改变了。如果需要存储更多的元素,就需要先创建一个更长的数组,再把原来数组里的值复制到新数组里。)
在 Go 语言中声明变量时,总会使用对应类型的零值来对变量进行初始化。
数组字面量声明数组:
//声明一个五个元素的整型数组
//用具体值初始化每一个元素
array:= [5]int{10,20,30,40,50}
如果使用...替代数组的长度,Go 语言会根据初始化时数组元素的数量来确定该数组的长度。
声明部分字面量数组:
//声明一个有5个元素的数组
//用具体值初始化索引为1和2的元素
//其余元素保持0值
array:=[5]int{1:20,2:20}
- 使用数组
数组的内存布局是连续的,所以数组是效率很高的数据结构。要访问数组里某个单独元素,使用[]运算符。
//声明一个包含5个元素的整型数组
//用具体值初始化为每个元素
array := [5]int{10,20,30,40,50}
//修改索引为2的元素的值
array[2]=35
声明一个所有元素都是指针的数组,使用*运算符就可以访问元素指针所指向的值。
//声明包含五个元素的指向整数得到数组
//用整型指针初始化索引为0和1的数组元素
array := [5]*int{0:new(int),1:new(int)}
//为索引为0和1的元素复制
*array[0] = 10
*array[1] = 20
- 复制数组
在Go语言里,数组是一个值。这意味着数组可以用在赋值操作中。变量名代表整个数组,因此,同样类型的数组可以赋值给另一个数组。
//声明一个5个元素的数组
var array1 [5]string
//声明第二个包含5个元素的字符串数组
array2 := [5]string{"Red","Blue","Green","Yellow","Pink"}
//把array2的值复制到array1
array1 = array2
复制后,这两个数组的值就完全一样。只有数组长度和每个元素的类型相同的数组才可以互相赋值
复制数组指针,只会复制指针的值,而不会复制指针所指向的值。
//声明一个3个元素的数组
var array1 [3]*string
//声明第二个包含3个元素的字符串数组
array2 := [3]*string{new{string},new{string},new{string}}
//使用颜色为每个元素赋值
*array2[0] = "Red"
*array2[1] = "Blue"
*array2[2] = "Green"
//把array2的值复制到array1
array1 = array2
- 多维数组
数组本身只有一个维度,不过可以组合多个数组创建多维数组。多维数组很容易管理具有父子关系的数据或者与坐标系相关联的数据
//声明一个二维整型数组,两个维度分别存储4个元素和2个元素
var array [4][2]int
//使用数组字面量来声明并初始化一个二维整型数组
array := [4][2]int{{10,11},{20,21},{30,32},{40,41}}
//声明并初始化外层数组中索引为1个和3的元素
array := [4][2]int{1:{0,21},3:{1:41}}
//声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1:{0:20},3:{1:41}}
访问多维数组
var array [2][2]int
array[0][0] = 10
array[0][1] = 20
- 在函数间传递数组
根据内存和性能来看,在函数间传递数组是一个开销很大的操作。
使用指针在函数间传递大数组。
切片
切片是一种数据结构,这种数据结构便于使用和管理数据集合,可以按需自动增长和缩小。切片的动态增长是通过内置函数append来实现的。
- 内部实现
切片有三个字段,分别是指向底层数组的指针、切片访问的元素的个数(长度)和切片允许增长的元素个数(容量)。
- 创建和初始化
(是否能提前知道切片需要的容量通常会决定要如何创建切片。)
make:
//创建一个字符串切片
//其长度和容量都是5个元素
slice := make([]string,5)
如果只指定长度,那么切片的容量和长度相等。也可以指定长度和容量。
//创建一个整型切片
//其长度为3个元素,容量为5个元素
slice := make([]int,3,5)
注意:不允许创建容量小于长度的切片。
切片字面量:初始的长度和容量会基于初始化时提供的元素的个数确定。
//创建字符串切片
//其长度和容量都是五个元素
slice := []string{"Red","Blue","Green","Yellow","Pink"}
//创建一个整型切片
//其长度和容量都是3个元素
silce := []int{10,20,30}
//使用空字符串初始化第100个元素 创建长度和容量都是100个元素的切片
slice := []int{99:""}
注意:如果在[]中指定了一个值,那么创建的就是数组而不是切片。
- nil和空切片
//创建nill整型切片 长度0 容量0
var slice []int
//使用make创建空的整型切片
slice := make([]int,0)
//使用切片字面量创建空格的整型切片
slice := []int{}
- 使用切片
使用[]操作符
slice := []int{10,20,30,40,50}
//改变索引为1的元素的值
slice[1] = 25
slice := []int{10,20,30,40,50}
newSlice := slice[1:3]
//newSlice的长度为2,容量为4
如上代码,它们共享同一段底层数组。第一个切片 slice 能够看到底层数组全部 5 个元素的容量,不过之后的 newSlice 就看不到。对于 newSlice ,底层数组的容量只有 4 个元素。
计算切片的长度和容量:
对底层数组容量是k的切片slice[i:j]来说
长度:j - i
容量:k - i
切片只能访问到其长度内的元素。
-
切片增长
函数 append 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。
//创建一个整型切片
slice := []int{10,20,30,40,50}
newslice := slice[1:3]
//使用原有的容量来分配一个新元素
//将新元素赋值为60
newslice = append(newSlice,60)
//newslice的长度变为3,容量依然是4,slice中索引为3的元素的值也变为60
如果切片的底层数组没有足够的可用容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。
函数 append 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是
会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%
的容量。
- 限制切片的容量
slice := source[2:3:4]
//长度为1,容量为2
[i:j:k]
内置函数append的参数。
//创建两个切片,并分别用两个整数进行初始化
s1 := []int{1,2}
s2 := []int{3,4}
fmt.Printf("%v\n",append(s1,s2...))
Output:
[1 2 3 4]
- 迭代切片
使用range关键字,配合for循环来迭代切片里的元素
package main
import "fmt"
func main(){
slice := []int{10,20,30,40}
for index,value := range slice {
fmt.Printf("Index:%d Value: %d\n",index,value)
}
}
output:
Index:0 Value: 10
Index:1 Value: 20
Index:2 Value: 30
Index:3 Value: 40
range会返回两个值,第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。
注意:range创建了每个元素的副本,而不是直接返回对该元素的引用。
range总是会从切片头部开始迭代,如果想对迭代做更多的控制,可以使用传统的for循环。
- len、cap
对于切片,len返回切片的长度,cap返回切片的容量。
- 多维切片
slice := [][]int{{10},{100,200}}
这种组合可以让用户创建非常复杂且强大的数据结构。已经学过的关于内置函数 append的规则也可以应用到组合后的切片上。
slice := [][]int{{10},{100,200}}
slice[0] = append(slice[0],,20)
slice变为:{{10,20},{100,200}}
- 在函数间传递切片
在函数间以值的方式进行传递,由于切片的尺寸很小,在函数间复制和传递切片成本也很低。
将切片复制到任意函数的时候,对底层数组大小都不会有英雄,复制时只会复制切片本身,不会涉及底层数组。
//分配包含100万个整型值的切片
slice := make([]int,le6)
//将slice传递给函数foo
slice = foo(slice)
//函数foo接收到一个整型切片,并返回这个切片
func foo(slice []int)[]int{
...
return slice
}
如图,函数间传递切片并不会传递底层数组。
映射
映射是一种数据结构,用于存储一系列无序的键值对。映射里基于键来存储值。
- 内部实现
映射是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。但映射是无序的集合,意味着没有办法预测键值对被返回的顺序。
- 创建和初始化
使用make:
//创建一个映射,键的类型是string,值的类型是int
dict := make(map[string]int)
//创建一个映射,键和值的类型都是string
//使用两个键值对初始化映射
dict := map[string]string{"Red":"da1337","Orange":"#e95a22"}
切片、函数以及包含切片的结构类型这些类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误。 但是切片可以作为映射的值。
//创建一个映射,使用字符串切片作为值
dict := map[int][]string{}
- 使用映射
为映射赋值
//创建一个空映射。用来存储颜色对应的十六进制代码
colors := map[string]string{} //空映射
colors["Red"] = "#da1337"
var colors map[string]string //nil映射
colors["Red"] = "#da1337" //报错
从映射取值时有两个选择。第一个选择是,可以同时获得值,以及一个表示这个键是否存在的标志。
从映射获取值并判断键是否存在
//获取Blue对应的值
value,exists := color["Blue"]
//判断键是否存在
if exists{
fmt.Println(value)
}
另一个选择是,只返回键对应的值,然后通过判断这个值是不是零值来确定键是否存在。
value := color["Blue"]
if value != ""{
fmt.Println(value)
}
在Go语言中,通过键来索引映射时,即便这个键不存在也总会返回一个值,这种情况下,返回的是该值对应的类型的零值。
迭代映射同样适用关键词range,但是返回的不是索引和值,而是“键值对”
colors := map[string]string{
"AliceBlue": "#f0f8ff",
"Coral": "#ff7F50",
"DarkGray": "#a9a9a9",
"ForestGreen": "#228b22",
}
for key,value := range colors {
fmt.Printf("key:%s Value:%s\n",key,value)
}
Output:
Key:Coral value:#ff7F50
Key:DarkGray value:#a9a9a9
Key:ForestGreen value:#228b22
Key:AliceBlue value:#f0f8ff
注意:range迭代只能用在映射存储的值都是非零值的情况
删除键值对(delet键)
delet(colors,"Coral")
- 在函数间传递映射
在函数间传递映射并不会创造出该映射的一个副本。
当传递映射给一个函数,并对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改,与切片类似。
小结
数组是构造切片和映射的基石。
GO语言切片经常用来处理数据的集合,映射用来处理具有键值对结构的数据。
内置函数 make 可以创建切片和映射,并指定原始的长度和容量。也可以直接使用切片和映射字面量,或者使用字面量作为变量的初始值。
切片有容量限制,不过可以使用内置的 append 函数扩展容量。
映射的增长没有容量或者任何限制
内置函数 len 可以用来获取切片或者映射的长度。
内置函数 cap (返回容量)只能用于切片。
通过组合,可以创建多维数组和多维切片。也可以使用切片或者其他映射作为映射的值。但是切片不能用作映射的键。
将切片或者映射传递给函数成本很小,并且不会复制底层的数据结构。