4. 数组、切片和映射(Go Tutorial)
数组、切片和映射(Go Tutorial)
4.1 数组的内部实现和基础功能
4.1.1 内部实现
在 Go 语言里,数据是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块。
数据存储的类型可以是内置类型,如 int 和 string 等,也可以是自定义类型。
由于数组的每个元素类型相同,又是连续分配,所以可以以固定速度索引数组中的任意数据,速度非常快。
4.1.2 声明和初始化
- 声明一个数组并初始化为零值
// 声明一个包含 5 个元素的整形数组
var array [5]int
- 使用数组字面量声明数组
// 声明一个包含5个元素的整形数组并使用具体值初始化每个元素
array := [5]int{10, 20, 30, 40, 50}
// `...` 表示容量由初始化值的数量决定
array := [...]int{10, 20, 30, 40}
- 声明数据并指定特定元素的值
// 声明一个有 5 个元素的数组,用具体值初始化索引为 1 和 2 的元素,其余元素保存零值
array := [5]int{1: 10, 2:20}
4.1.3 使用数组
- 访问数组元素
array := [5]int{10, 20, 30, 40, 50}
// 修改数组中索引为 2 的元素的值
array[2] = 35
- 访问指针数组的元素
// 声明包含 5 个元素的指向整形的数组
array := [5]*int{0: new(int), 1: new(int)}
// 为索引 0 和 1 的元素赋值
*array[0] = 10
*array[1] = 20
- 把一个指针数组赋值给另一个
var array1 [3]*string array2 := [3]*string{new(string), new(string), new(string)} *array2[0] = "a" *array2[1] = "b" *array3[2] = "c" array1 = array2
数组变量的类型包括长度和每个元素的类型,这有这两部分都相同的数组,才能互相赋值,否则编译器会报错
4.1.4 多维数组
- 声明二维数组
// 声明一个二维数组,两个维度分别为存储 4 个元素和 2 个元素
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整形数组
array := [4][2]int{{10, 11}, {12, 13}, {14, 15}, {16, 17}}
// 声明并初始化外层数组中索引为 1 和 3 的元素
array := [4][2]int{1: {20, 21}, 3: {30, 31}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: (0: 20), 3: (1: 31)}
- 访问二维数组
var array [2][2]int
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
arrat[1][1] = 40
4.1.5 在函数之间传递数组
在函数之间传递数组是一个开销很大的操作。因为在函数之间传递数组时,总是以值的方式传递的。
- 使用指针在函数间传递大数组
var array [le6]int foo(&array) func foo(array *[le6]int) { // .... }
虽然将数组的地址传给函数,不会带来很大的内存开销,但是由于传递的是指针,如果改变指针指向的值,会改变共享的内存。
4.2 切片的内部实现和基础功能
4.2.1 内部实现
切片是一种便于使用和管理的数据集合,其是围绕动态数组的概念构建的,可以按需自动增长和缩小。
切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。
切片有 3 个字段的数据节后,这些数据结构包含 Go 语言需要操作底层数组的元数据。
这 3 个字段分别是指向底层数组的指针、切片的长度、切片的容量。
4.2.2 创建和初始化
-
make 和 切片字面量
- 使用长度声明一个字符串切片
// 创建一个字符串切片 // 其长度和容量都是 5 个元素 slice := make([]string, 5)
如果只指定长度,那么容量和长度相等
- 使用长度和容量声明整形切片
// 创建一个整形切片 // 其长度为 4,容量为 7 slice := make([]int, 4, 7)
不允许创建容量小于长度的切片
- 通过字面量来声明切片
slice := []string{"red", "blue", "yellow"}
slice := []int{10, 20, 30}
- 使用切片字面量,可以设置初始化长度和容量
// 创建字符串切片
// 使用空字符串初始化第 100 个元素
slice := []string{99: ""}
- 声明数组和切片的不同
// 声明数组 array := [3]int{10, 20, 30} // 声明切片 slice := []int{10, 20, 30}
如果在
[]
运算符里指定了一个值,那么创建的就是数组而不是切片
-
nil 和空切片
- 创建 nil 切片
// 创建 nil 整形切片 var slice []int
需要描述一个不存在的切片时,nil 切片会很好用
- 创建空切片
slice := make([]int, 0) slice := []int{}
在表示空集合时,空切片很有用
4.2.3 使用切片
4.2.3.1 赋值和切片
对切片元素赋值与数组元素的赋值一样
- 使用切片字面量来声明切片
slice := []int{10, 20, 30}
slice[1] = 21
- 使用切片创建切片
slice := []int{10, 20, 30} newSlice := slice[1:2]
上面两个切片共享一段底层数组,但通过不同的切片会看到底层数组的不同部分
- 计算长度和容量
对于底层数组容量是 k 的切片 slice[i:j] 来说
长度:j - i
容量:k - i
- 修改切片内容可能导致的结果
slice := []int{10, 20, 30}
newSlice := slice[1:2]
newSlice[0] = 21
// output:21
fmt.Pringln(slice[1])
4.2.3.2 切片增长
相对数组而言,使用切片的一个好处是,可以按需增长切片的容量。
Go 语言内置的 append 函数会处理增加长度时的所有操作细节。append 调用返回时,会返回一个包含修改结果的新切片。
函数 append 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决与被操作的切片是否有可用的容量。
- 使用 append 向切片增加元素
slice := []int{10, 20, 30} newSlice := slice[0 : 1] newSlice = append(newSlice, 21) // [10 21] fmt.Println(newSlice) // [10 21 30] fmt.Println(slice)
因为 newSlice 在底层数组还有容量可以,所以和 slice 共用的是同一个底层数组,对 newSlice 的修改就是修改共用的底层数组
- 使用 append 同时增加长度和容量
slice := []int{10, 20, 30, 40} newSlice := append(slice, 50) newSlice[0] = 11 // [10 20 30 40] fmt.Println(slice) // [11 20 30 40 50] fmt.Println(newSlice)
如果切片的底层数组没有足够的可以容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。
在上面的例子中,newSlice 与 slice 不再共用同一个底层数组。
append 函数会智能的处理底层数组的容量增长:当切片的容量小于 1000 个元素时,总是成倍的增长容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是每次增长 25% 的容量。
4.2.3.3 创建切片的 3 个索引
在创建切片时,还可以使用第三个索引选项,第三个索引选项可以用来控制新切片的容量,其目的并不是要增加容量,而是要限制容量,这为底层数组提供了一定的保护,可以更好的控制追加操作。
- 使用 3 个索引创建切片
source := []string{"apple", "orange", "banana", "grape", "plum"} slice := source[2 : 3 : 4]
对于 slice[i : j : k],其长度为 j - i,容量为 k - i。
当设置容量大于已有容量时编译器会报错
- 设置长度和容量一致
source := []string{"apple", "orange", "banana", "grape", "plum"} slice := source[1:3:3] slice = append(slice, "kiwi", "hello") // [orange banana kiwi hello] fmt.Println(slice)
如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 函数操作创建新的底层数组,与原有的底层数组分离。
- 将一个切片追加到另一个切片
s1 := []int{1, 2}
s2 := []int{2, 4}
// [1 2 2 4]
fmt.Printf("%v\n", append(s1, s2...))
4.2.3.4 迭代切片
Go 语言里可以使用关键字 range,它可以配合关键字 for 来迭代切片里的元素
- 使用 range 迭代切片
slice := []int{10, 20, 30}
for _, value := range slice {
fmt.Printf("value: %d\n", value)
}
for index, value := range slice {
fmt.Printf("index: %d, value: %d\n", index, value)
}
- 使用传统的 for 循环迭代切片
slice := []int{10, 20} for index := 2; index < len(slice); index++ { fmt.Printf("index: %d, value: %d\n", index, slice[index]) }
内置函数 len 和 cap,可以用于处理数组、切片和通道。对于切片,len 返回长度,cap 返回容量
4.2.4 多维切片
- 声明多维切片
slice := [][]int{{10}, {20, 30}}
slice[0] = append(slice[0], 20)
// [[10 20] [20 30]]
fmt.Println(slice)
4.2.5 在函数间传递切片
由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会赋值切片本身,所以在函数间传递会非常快速、简单。
package main
import (
"fmt"
)
func test(slice []int) {
var newSlice []int
newSlice = slice
newSlice = slice[0:len(slice):len(slice)]
newSlice = append(newSlice, 40)
newSlice[0] = 10
fmt.Println(newSlice)
}
func main() {
slice := []int{1, 2, 3, 4, 5}
test(slice[0:3])
fmt.Println(slice)
// [10 2 3 40]
// [1 2 3 4 5]
}
4.3 映射的内部实现和基础功能
映射是一种存储一系列无序键值对的数据结构
4.3.1 内部实现
只需记住一件事:映射是一个存储键值对的无序集合
4.3.2 创建和初始化
- 使用 make 声明映射
dict := make(map[string]int)
dict := map[string]string{"name": "li", "gender": "male"}
- 声明一个存储字符串切片的映射
// 创建一个映射,使用字符串切片作为值
dict := map[int][]string{}
4.3.3 使用映射
func main() {
// 创建一个空映射并赋值
colors := map[string]string{}
colors["red"] = "RED"
fmt.Println(colors)
// 对 nil 映射赋值时,会报错
var colors2 map[string]string
colors2["red"] = "RED"
fmt.Println(colors2)
}
从映射取值时有两个选择。
- 第一个选择是,可以同时获得值,以及一个表示这个是否存在的标志
value, exists := colors["blue"]
// 这个键存在吗
if exists {
fmt.Println(value)
}
- 另一个选择是,只返回键对应的值,然后判断这个值是否是零值来判断键是否存在
value := colors["blue"]
if value != "" {
fmt.Println(value)
}
使用 range 关键字迭代映射
func main() {
colors := map[string]string{"name": "lilei", "gender": "male", "country": "china"}
for key, value := range colors {
fmt.Printf("%s is %s\n", key, value)
}
}
从映射中删除一项
func main() {
colors := map[string]string{"name": "liLei", "gender": "male", "country": "china"}
delete(colors, "name")
for key, value := range colors {
fmt.Printf("%s is %s\n", key, value)
}
}
4.3.4 在函数间传递映射
将映射传递给函数成本很小,并且不会赋值底层的数据结构
package main
import (
"fmt"
)
func removeColors(colors map[string]string, key string) map[string]string {
delete(colors, key)
return colors
}
func iterator(colors map[string]string) {
for key, value := range colors {
fmt.Printf("%s is %s\n", key, value)
}
}
func main() {
colors := map[string]string{"name": "liLei", "gender": "male", "country": "china"}
iterator(removeColors(colors, "name"))
iterator(colors)
// output is:
// gender is male
// country is china
// gender is male
// country is china
}
上一篇: C++的inline
下一篇: c++内联函数