数组,切片和映射
# 数组
# 内部实现
数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连 续块。数组存储的类型可以是内置类型,如整型或者字符串,也可以是某种结构类型。
# 声明和初始化
- 方法一:普通方式 声明数组时需要指定内部存储的数据的类型,以及需要存储的元素的数量,这个数量也称为数组的长度。
var array [5]int
一旦声明,数组里存储的数据类型和数组长度就都不能改变了。如果需要存储更多的元素,就需要先创建一个更长的数组,再把原来数组里的值复制到新数组里。
当数组初始化时,数组内每个元素都初始化为对应类型的零值。
- 方法二:使用数组字面量 这是快速创建数组并初始化的方式。数组字面量允许声明数组里元素的数量同时指定每个元素的值
array := [5]int{10,20,30,40,50}
- 方法三:...替代数组的长度 Go 语言会根据初始化时数组元素的数量来确定该数组的长度
// 容量由初始化值的数量决定
array := [...]int{10, 20, 30, 40, 50}
2
- 方法四:声明数组并指定特定元素的值
array := [5]int{1: 10, 2: 20}
# 使用
- 要访问数组里某个单独元素,使用
[]
运算符
// 声明一个包含 5 个元素的整型数组
// 用具体值初始为每个元素
array := [5]int{10, 20, 30, 40, 50}
// 修改索引为 2 的元素的值
array[2] = 35
2
3
4
5
- 使用
*
运算符就可以访问元素指针所指向的值
// 声明包含 5 个元素的指向整数的数组
// 用整型指针初始化索引为 0 和 1 的数组元素
array := [5]*int{0: new(int), 1: new(int)}
// 为索引为0和1的元素赋值
*array[0] = 10
*array[1] = 20
2
3
4
5
6
- 数组间赋值
var array1 [5]string
array2 := [5]string{"Red", "Blue", "Green", "Yellow", "Pink"}
array1 = array2
2
3
只有类型(包括数组长度和每个元素的类型)相同的数组,才能互相赋值
- 数组指针复制,只会复制指针的值,不会复制指针所指向的值。
# 多维数组
// 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化外层数组中索引为 1 个和 3 的元素
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
// 访问
// 设置每个元素的整型值 array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
array[1][1] = 40
// 将 array1 的索引为 1 的维度复制到一个同类型的新数组里
var array3 [2]int = array1[1]
// 将外层数组的索引为 1、内层数组的索引为 0 的整型值复制到新的整型变量里
var value int = array1[1][0]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 在函数间传递数组
在函数之间传递变量时,总是以值传递的方式进行传递的。因此需要使用指针来传递大数组
//分配一个需要8 MB的数组
var array [1e6]int
// 将数组的地址传递给函数
foo foo(&array)
// 函数 foo 接受一个指向 100 万个整型值的数组的指针
func foo(array *[1e6]int) {
... }
2
3
4
5
6
7
# 切片
切片是一种数据结构,这种数据结构便于使用和管理数据集合。
切片的动态增长是通过内置函数 append 来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。
因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
# 内部实现
切片有3个字段的数据结构:
# 创建和初始化
- make和切片字面量
// 创建一个字符串切片,其长度和容量都是 5 个元素
slice := make([]string, 5)
// 创建一个整型切片,其长度为3个元素,容量为5个元素,长度要小等于容量
slice := make([]int, 3, 5)
// 创建字符串切片,其长度和容量都是 5 个元素
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
// 创建一个整型切片,其长度和容量都是 3 个元素
slice := []int{10, 20, 30}
// 创建字符串切片,使用空字符串初始化第 100 个元素
slice := []string{99: ""}
2
3
4
5
6
7
8
9
10
11
12
13
如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值的时候,才会创建切片。
array := [3]int{10, 20, 30}
// 创建长度和容量都是 3 的整型切片
slice := []int{10, 20, 30}
2
3
- nil和空切片
只要在声明时不做任何初始化,就会创建一个
nil
切片
// 创建 nil 整型切片
var slice []int
// 使用 make 创建空的整型切片
slice := make([]int, 0)
// 使用切片字面量创建空的整型切片
slice := []int{}
2
3
4
5
6
7
# 使用
- 赋值和切片
// 创建一个整型切片,其容量和长度都是 5 个元素
slice := []int{10, 20, 30, 40, 50}
// 改变索引为 1 的元素的值
slice[1] = 25
// 创建一个新切片,其长度为 2 个元素,容量为 4 个元素
newSlice := slice[1:3]
2
3
4
5
6
7
切片的本质就是把底层数组切除一部分,故而称为切片。若切片从同一个底层数组切除的话,则两个切片共享同一个底层数组,即如果一个切片改变了底层数组的共享部分,另一个切片也会受到影响。
提示
对底层数组容量是k的切片slice[i:j]来说, 长度:j-i 容量:k-i
切片只能访问到其长度内的元素。试图访问超出其长度的元素将会导致语言运行时异常。
- 切片增长。
相对于数组而言,使用切片的一个好处是,可以按需增加切片的容量。Go 语言内置的 append 函数会处理增加长度时的所有操作细节。
// 创建一个整型切片,其长度和容量都是 5 个元素
slice := []int{10, 20, 30, 40, 50}
// 创建一个新切片,其长度为 2 个元素,容量为 4 个元素
newSlice := slice[1:3]
// 使用原有的容量来分配一个新元素,将新元素赋值为 60
newSlice = append(newSlice, 60)
2
3
4
5
6
如果切片的底层数组没有足够的可用容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。
- 创建切片时的3个索引
第三个索引可以用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。
source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
slice := source[2:3:4]
2
第3个索引值为容量的索引值
- 迭代切片
slice := []int{10, 20, 30, 40}
// 迭代每一个元素,并显示其值
for index, value := range slice {
fmt.Printf("Index: %d Value: %d\n", index, value)
}
2
3
4
5
6
当迭代切片时,关键字 range 会返回两个值。第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值的一份副本。
# 多维切片
// 创建一个整型切片的切片
slice := [][]int{{10}, {100, 200}}
2
# 在函数间使用切片
在函数间传递切片就是要在函数间以值的方式传递切片。
# 映射
映射是一种数据结构,用于存储一系列无序的键值对集合。
映射里基于键来存储值。映射能够基于键快速检索数据。键就像索引一样,指向与该键关联的值。
# 内部实现
映射是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。
映射是无序的集合,意味着没有办法预测键值对被返回的顺序。无序的原因是映射的实现使用了散列表。
# 创建和初始化
// 创建一个映射,键的类型是 string,值的类型是 int
dict := make(map[string]int)
// 创建一个映射,键和值的类型都是 string,使用两个键值对初始化映射
dict := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}
2
3
4
5
映射的键可以是任何值。这个值的类型可以是内置的类型,也可以是结构类型,只要这个值可以使用==
运算符做比较
# 使用
// 创建一个空映射,用来存储颜色以及颜色对应的十六进制代码
colors := map[string]string{}
// 将 Red 的代码加入到映射
colors["Red"] = "#da1337"
// 获取键 Blue 对应的值
value, exists := colors["Blue"]
// 这个键存在吗?
if exists {
fmt.Println(value)
}
// 创建一个映射,存储颜色以及颜色对应的十六进制代码
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)
}
// 删除键为 Coral 的键值对
delete(colors, "Coral")
// 显示映射里的所有颜色
for key, value := range colors {
fmt.Printf("Key: %s Value: %s\n", key, value)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
nil 映射不能用于存储键值对。
# 在函数间使用映射
在函数间传递映射时是进行值传递。