第 6 章 容器类型
6.1 数组
数组 是一个由 固定长度 的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组。和数组对应的类型是 slice(切片) ,它是可以动态的增长和收缩的序列, slice
功能也更灵活,下面我们再讨论 slice
。
6.1.1 数组声明
可以使用 [n]Type
来声明一个数组。其中 n
表示数组中元素的数量, Type
表示每个元素的类型。
package main
import "fmt"
func test01() {
// 声明时没有指定数组元素的值, 默认为零值
var arr [5]int
fmt.Println(arr)
arr[0] = 1
arr[1] = 2
arr[2] = 3
fmt.Println(arr)
}
func test02() {
// 直接在声明时对数组进行初始化
var arr1 = [5]int{15, 20, 25, 30, 35}
fmt.Println(arr1)
// 使用短声明
arr2 := [5]int{15, 20, 25, 30, 35}
fmt.Println(arr2)
// 部分初始化, 未初始化的为零值
arr3 := [5]int{15, 20} // [15 20 0 0 0]
fmt.Println(arr3)
// 可以通过指定索引,方便地对数组某几个元素赋值
arr4 := [5]int{1: 100, 4: 200}
fmt.Println(arr4) // [0 100 0 0 200]
// 直接使用 ... 让编译器为我们计算该数组的长度
arr5 := [...]int{15, 20, 25, 30, 35, 40}
fmt.Println(arr5)
}
func test03() {
// 特别注意数组的长度是类型的一部分,所以 [3]int 和 [5]int 是不同的类型
arr1 := [3]int{15, 20, 25}
arr2 := [5]int{15, 20, 25, 30, 35}
fmt.Printf("type of arr1 is %Tn", arr1)
fmt.Printf("type of arr2 is %Tn", arr2)
}
func test04() {
// 定义多维数组
arr := [3][2]string{
{"1", "Go语言极简一本通"},
{"2", "Go语言微服务架构核心22讲"},
{"3", "从0到Go语言微服务架构师"}}
fmt.Println(arr) // [[15 20] [25 22] [25 22]]
}
func main() {
test01()
test02()
test03()
test04()
}
6.1.2 数组长度
使用内置的 len
函数将返回数组中元素的个数,即数组的长度。
func arrLength() {
arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
fmt.Println("数组的长度是:", len(arr)) //数组的长度是: 3
}
6.1.3 数组遍历
使用 for range
循环可以获取数组每个索引以及索引上对应的元素。
func showArr() {
arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
for index, value := range arr {
fmt.Printf("arr[%d]=%sn", index, value)
}
for _, value := range arr {
fmt.Printf("value=%sn", value)
}
}
6.1.4 数组是值类型
Go 中的数组是值类型而不是引用类型。当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,不会影响原始数组。
代码语言:go复制func arrByValue() {
arr := [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
copy := arr
copy[0] = "Golang"
fmt.Println(arr)
fmt.Println(copy)
}
6.2 切片(Slice)
切片是对数组的一个连续片段的引用,所以切片是一个引用类型。切片 本身不拥有任何数据,它们只是对现有数组的引用,每个切片值都会将数组作为其底层的数据结构。slice 的语法和数组很像,只是没有固定长度而已。
6.2.1 创建切片
使用 []Type
可以创建一个带有 Type
类型元素的切片。
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
你也可以使用 make
函数构造一个切片,格式为 make([]Type, size, cap)
。
numList := make([]int, 3, 5)
当然,我们可以通过对数组进行片段截取创建一个切片。
代码语言:go复制arr := [5]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师", "微服务", "分布式"}
var s1 = arr[1:4]
fmt.Println(arr) // [Go语言极简一本通 Go语言微服务架构核心22讲 从0到Go语言微服务架构师 微服务 分布式]
fmt.Println(s1) // [Go语言微服务架构核心22讲 从0到Go语言微服务架构师 微服务]
6.2.2 切片的长度和容量
一个 slice 由三个部分构成:指针 、 长度 和 容量 。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。
内置的 len
和 cap
函数分别返回 slice 的长度和容量。
s := make([]string, 3, 5)
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 5
如果切片操作超出上限将导致一个 panic
异常。
s := make([]int, 3, 5)
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3
Tips:
- 由于 slice 是引用类型,所以你不对它进行赋值的话,它的默认值是
nil
var numList []int
fmt.Println(numList == nil) // true
- 切片之间不能比较,因此我们不能使用
==
操作符来判断两个 slice 是否含有全部相等元素。特别注意,如果你需要测试一个 slice 是否是空的,使用len(s) == 0
来判断,而不应该用s == nil
来判断。
6.2.3 切片元素的修改
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。
代码语言:go复制func modifySlice() {
var arr = [...]string{"Go语言极简一本通", "Go语言微服务架构核心22讲", "从0到Go语言微服务架构师"}
s := arr[:] //[0:len(arr)]
fmt.Println(arr) //[Go语言极简一本通 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
fmt.Println(s) //[Go语言极简一本通 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
s[0] = "Go语言"
fmt.Println(arr) //[Go语言 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
fmt.Println(s) //[Go语言 Go语言微服务架构核心22讲 从0到Go语言微服务架构师]
}
这里的 arr[:]
没有填入起始值和结束值,默认就是 0
和 len(arr)
。
6.2.4 追加切片元素
使用 append
可以将新元素追加到切片上。append
函数的定义是 func append(slice []Type, elems ...Type) []Type
。其中 elems ...Type
在函数定义中表示该函数接受参数 elems
的个数是可变的。这些类型的函数被称为可变函数。
func appendSliceData() {
s := []string{"Go语言极简一本通"}
fmt.Println(s)
fmt.Println(cap(s))
s = append(s, "Go语言微服务架构核心22讲")
fmt.Println(s)
fmt.Println(cap(s))
s = append(s, "从0到Go语言微服务架构师", "分布式")
fmt.Println(s)
fmt.Println(cap(s))
s = append(s, []string{"微服务", "分布式锁"}...)
fmt.Println(s)
fmt.Println(cap(s))
}
当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。
6.2.5 多维切片
类似于数组,切片也可以有多个维度。
代码语言:go复制func mSlice() {
numList := [][]string{
{"1", "Go语言极简一本通"},
{"2", "Go语言微服务架构核心22讲"},
{"3", "从0到Go语言微服务架构师"},
}
fmt.Println(numList)
}
6.3 Map
在 Go 语言中,map 是散列表(哈希表)的引用。它是一个拥有键值对元素的无序集合,在这个集合中,键是唯一的,可以通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量时间完成的。所有可比较的类型,如 整型
,字符串
等,都可以作为 key
。
6.3.1 创建 Map
使用 make
函数传入键和值的类型,可以创建 map 。具体语法为 make(map[KeyType]ValueType)
。
// 创建一个键类型为 string 值类型为 int 名为 scores 的 map
scores := make(map[string]int)
steps := make(map[string]string)
我们也可以用 map 字面值的语法创建 map ,同时还可以指定一些最初的 key/value :
代码语言:go复制var steps2 map[string]string = map[string]string{
"第一步": "Go语言极简一本通",
"第二步": "Go语言微服务架构师核心22讲",
"第三步": "从0到Go语言微服务架构师",
}
fmt.Println(steps2)
或者
代码语言:go复制steps3 := map[string]string{
"第一步": "Go语言极简一本通",
"第二步": "Go语言微服务架构师核心22讲",
"第三步": "从0到Go语言微服务架构师",
}
fmt.Println(steps3)
6.3.2 Map 操作
- 添加元素
// 可以使用 `map[key] = value` 向 map 添加元素。
steps3["第四步"] = "总监"
- 更新元素
// 若 key 已存在,使用 mapkey = value 可以直接更新对应 key 的 value 值。
steps3"第四步" = "CTO"
代码语言:txt复制
- 获取元素
// 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。
fmt.Println(steps3["第四步"] )
- 删除元素
//使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。
delete(steps3, "第四步")
- 判断 key 是否存在
// 如果我们想知道 map 中的某个 key 是否存在,可以使用下面的语法:value, ok := map[key]
v3, ok := steps3["第三步"]
fmt.Println(ok)
fmt.Println(v3)
v4, ok := steps3["第四步"]
fmt.Println(ok)
fmt.Println(v4)
这个语句说明 map
的下标读取可以返回两个值,第一个值为当前 key
的 value
值,第二个值表示对应的 key
是否存在,若存在 ok
为 true
,若不存在,则 ok
为 false
。
- 遍历 map
// 遍历 map 中所有的元素需要用 for range 循环。
for key, value := range steps3 {
fmt.Printf("key: %s, value: %dn", key, value)
}
- 获取 map 长度
// 使用 len 函数可以获取 map 长度
func createMap() {
//...
fmt.Println(len(steps3)) // 4
}
6.3.3 map 是引用类型
当 map
被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
func mapByReference() {
steps4 := map[string]string{
"第一步": "Go语言极简一本通",
"第二步": "Go语言微服务架构师核心22讲",
"第三步": "从0到Go语言微服务架构师",
}
fmt.Println("steps4: ", steps4)
// steps4: map[第一步:Go语言极简一本通 第三步:从0到Go语言微服务架构师 第二步:Go语言微服务架构师核心22讲]
newSteps4 := steps4
newSteps4["第一步"] = "Go语言极简一本通-222"
newSteps4["第二步"] = "Go语言微服务架构师核心22讲-222"
newSteps4["第三步"] = "从0到Go语言微服务架构师-222"
fmt.Println("steps4: ", steps4)
// steps4: map[第一步:Go语言极简一本通-222 第三步:从0到Go语言微服务架构师-222 第二步:Go语言微服务架构师核心22讲-222]
fmt.Println("newSteps4: ", newSteps4)
// newSteps4: map[第一步:Go语言极简一本通-222 第三步:从0到Go语言微服务架构师-222 第二步:Go语言微服务架构师核心22讲-222]
}
当 map
作为函数参数传递时也会发生同样的情况。