本章介绍 Go 程序的基本组成部分:数组、切片和映射,它们也是常用的数据类型。
4.1 数组 Arrays
数组是由相同数据类型的数据项组成的长度固定、顺序固定的集合,数据项是数组的元素,数组的长度也是数组类型的组成部分。在编程中,数组通常用来对数据进行逻辑分组。
Go 中创建数组,初始化时必须声明长度和数据类型:
var blog [2]string
释义:
使用 var 关键字定义名为 blog 的数组
数组的长度为2
数组里存储 string 的数据类型
为 blog 数组中的元素分配字符串:
blog[0] = "https://www.aiops.red"
blog[1] = "sretech"
变量名后 [] 里的数字是数组元素的索引,表示分配的值在数组中的位置。索引从0开始,因此要访问数组的第一个元素,写法:blog[0];访问第二个元素:blog[1]。
Go 语言的索引形式采用了左闭右开区间,这和大多数编程语言相同。如果我们要访问 blog 里的所有元素,可以使用 blog[0:2]的写法,这种写法包括索引0处的元素,不包括索引2处的元素,这就叫左闭右开。比如 arrays[m:n],0 ≤ m ≤ n ≤ len(s),包含 n-m 个元素。
使用变量名和索引值打印对应数组元素的值:
fmt.Println(blog[0])
fmt.Println(blog[1])
仅使用变量名打印数组中的所有值:
fmt.Println(blog)
完整代码示例:
package main
import "fmt"
func main() {
var blog [2]string
blog[0] = "https://www.aiops.red"
blog[1] = "sretech"
fmt.Println(blog[0])
fmt.Println(blog[1])
fmt.Println(blog)
}
数组在定义时,必须指定其长度,且添加到该数组的元素数量不能超出数组的长度,否则会编译错误。
4.1.1 二维数组
数组中的元素也可以是数组,称为二维数组:
arrays := [3][2]int{{1, 2}, {3, 4}, {5, 6}}
完整代码示例:
package main
import "fmt"
func main() {
arrays := [3][2]int{{1, 2}, {3, 4}, {5, 6}}
fmt.Println(arrays)
fmt.Println(arrays[1])
fmt.Println(arrays[1][1])
}
4.2 切片 Slices
数组虽是 Go 的重要组成部分,但在 Go 中使用切片的情况更为常见。切片可以看作是基础数组的连续段,按顺序提供对数组元素的访问。既然这样,为什么会存在切片,而不是直接使用数组呢?
因为 Go 中使用数组有一些限制,在介绍数组部分的示例中,我们不能向 blog 数组添加超过其长度的元素,并且 blog 的数组长度是固定的。切片比数组更加灵活,可以向切片添加元素、删除元素和复制元素,并且切片的长度是可扩容的。可以把切片看做是对数组的封装,既保留的数组的完整功能,又更易于使用。
声明一个长度为2,数据类型是 string 的空切片:
var blog = make([]string, 2)
释义:
使用 var 关键字对变量 blog 进行初始化
用 Go 内置的 make 函数初始化一个切片,第一个参数代表切片的数据类型,第二个参数代表切片的长度。此示例中,切片包含了两个字符串类型的元素。
切片被分配给 blog 变量
在初始化切片后,可以使用和数组相同的赋值方法:
blog[0] = "https://www.aiops.red"
blog[1] = "sretech"
打印切片的方式也和打印数组相同:
fmt.Println(blog[0])
fmt.Println(blog[1])
到目前为止,切片看起来和数组相似。但是切片的不同之处在于添加和删除元素的功能。
4.2.1 向切片添加元素
Go 提高了内置的 append 函数作为扩展切片长度的一种方式:
blog = append(blog, "https://golang.google.cn")
fmt.Println(blog[2])
必要时可以使用 append 调整切片大小,且无需考虑实现的复杂性。此示例将 blog 切片的大小从两个元素扩展为三个元素,并将 https://golang.google.cn
分配给索引2的元素:
package main
import "fmt"
func main() {
var blog = make([]string, 2)
blog[0] = "https://www.aiops.red"
blog[1] = "sretech"
blog = append(blog, "https://golang.google.cn")
fmt.Println(blog)
}
append 函数是可变参函数,可以通过 append 函数将多个值附加给切片。
blog := append(blog, "https://golang.google.cn", "golang.aiops.red", "gitlab.com")
这会调整 blog 切片的大小,并按顺序将值分配给新创建的元素。
完整示例:
package main
import "fmt"
func main() {
var blog = make([]string, 2)
blog[0] = "https://www.aiops.red"
blog[1] = "sretech"
blog = append(blog, "https://golang.google.cn", "golang.aiops.red", "gitlab.com")
fmt.Println(blog)
}
4.2.2 删除切片中的元素
append 也可用来删除切片中的元素,以下示例将删除索引2处的元素:
blog = append(blog[:2], blog[3:]...)
检查 blog 元素删除前后的长度,以确定它已正确的调整了大小。
package main
import "fmt"
func main() {
var blog = make([]string, 2)
blog[0] = "https://www.aiops.red"
blog[1] = "sretech"
blog = append(blog, "https://golang.google.cn", "golang.aiops.red", "gitlab.com")
fmt.Println(blog)
blog = append(blog[0:2], blog[3:]...)
fmt.Println(blog)
}
4.2.3 从切片复制元素
使用内置的 copy 函数,可以从切片中复制全部或部分元素。要将一个切片复制到另一个切片,两个切片的数据类型必须相同:
package main
import "fmt"
func main() {
var blog = make([]string, 3)
blog[0] = "aiops.red"
blog[1] = "sretech"
blog[2] = "golang.org"
var blogChinese = make([]string, 2)
copy(blogChinese, blog)
fmt.Println(blog)
fmt.Println(blogChinese)
}
因为新切片的长度是2,copy 函数将 blog 切片中的前两个元素复制到新切片中,如果修改了一个切片中的元素,则对其他切片没有影响。也可以将单个元素或一系列元素复制到新切片中。在以下示例中,将从索引1处开始复制元素:
copy(blogChinese, blog[1:])
4.2.4 nil 切片与空切片
定义 nil 切片:
var nilSlice []int
定义空切片:
var emptySlice []int = []int{}
在 Go 中, 空切片和 nil 切片在定义上虽然不同,但操作上没有区别。
4.2.5 切片的长度和容量
在定义切片时,可以指定其长度及容量:
var blog = make([]string, 2, 8)
定义了长度为2,容量是8的切片。
可以通过内置函数 len()查看切片的长度,cap()查看切片的容量。
4.3 映射 Maps
数组和切片是可以用索引取值的有序元素集合。映射是由键而不是索引取值的无序元素组。如果你有其他编程经验,可以把映射理解为字典、散列或关联数组。因为可以直接通过键检索数据,所以映射可以非常有效的查找信息片段。简单的说,映射就是由键值对组成的一种无序的数据结构,通过 key 操作 value。
出始化一个空映射:
var players = make(map[string]int)
释义:
var 关键字初始化 players 变量
使用内置的 make 函数初始化一个映射,键数据类型是 string,值数据类型是 int
空映射分配给 players 变量
将键值对分配给空映射 players:
players["Curry"] = 30
players["James"] = 23
players["Kobe"] = 24
变量后的方括号里是键名,等号右侧是键对应的值。
在映射中,使用键作为索引,查找对应的值:
fmt.Println(players["Kobe"])
和数组、切片一样,要想打印映射中的所有键值对,只需映射名即可:
fmt.Println(players)
可以将元素动态添加到映射中,而无需调整其大小。这是 Go 的一个功能展现,Go 语言的行为更像是像 Ruby 或 Python 这样的动态编程语言,而不是类似 C 的语言。
但如果你清楚的知道,你需要使用多大容量的映射时,还是建议你在初始化映射时指定其容量大小。因为切片和映射都是自增长度的存储,在每次自增时,会分配新的内存空间,然后拷贝数据。这样会有消耗。在初始化时把容量初始化到我们需要的大小,会有效提高性能:
players := mmake(map[string]int, 3) // 3 就是初始化 players 时指定的容量
map 可以使用 len 函数求长度,但不能使用 cap 函数计算容量。
4.3.1 增加映射的元素
通过键赋值,如果键存在则是更新值的操作,如果键不存在,则是增加元素操作:
players["KD"] = 35
4.3.2 从映射中删除元素
delete 内置函数可以从映射中删除元素:
delete(players, "Kobe")
package main
import "fmt"
func main() {
var players = make(map[string]int)
players["Curry"] = 35
players["James"] = 23
players["Kobe"] = 24
fmt.Println(players["Curry"])
fmt.Println(players["Kobe"])
fmt.Println(players)
delete(players, "Kobe")
fmt.Println(players)
}
4.3.3 遍历映射中的元素
使用 for range 语句,对映射进行遍历:
// for range
for key, value := range players {
fmt.Println(key, value)
}
for key := range players {
fmt.Println(key, players[key])
}
4.3.4 映射中的 nil 与空值
在映射中,nil 不能进行赋值操作,所以在初始化映射的时候,尽量不要定义 nil 映射,而是定义空映射。
var nilMap map[string]string
//panic: assignment to entry in nil map
var emptyMap = make(map[string]int)
4.3.5 映射与工厂模式
工厂模式是设计模式最常用的方式之一,Go 中,函数是一等公民,可以作为参数传递。
m := map[int]func(num int)int{}
// m 的 key 是 int 类型,值是一个传入参数为 int,返回值为 int 的函数类型
m[1] = func(num int) int {return num}
m[1] = func(num int) int {return num * num}
m[1] = func(num int) int {return num * num * num}
fmt.Println(m[1](2), m[2](2), m[3](2))
4.4 总结
通过本章的学习,你可以在 Go 中定义数组、切片和映射了。并且应该也理解了,除非特定需求,尽量使用切片而非数组。虽然本章更多的是理论性的,而非实践性的,但数组、切片和映射是 Go 编程的基本结构块,理解它们,对以后的编程大有裨益。