数组(Array)
数组的定义
数组是一种线性数据结构,是一个存放相同类型数据的定长序列。数组中的元素在内存中是连续存储的,每个元素占用的内存大小相同,且每个元素都有一个唯一的索引,通过索引可以快速访问到对应的元素。
在 Go 语言中,数组的声明形式为:var 数组名 [数组长度]数据类型
其中数据类型也可以是数组,也就是数组类型的数组,这样就构成了多维数组。和变量的声明一样,数组在声明是会初始化变量类型的零值。
示例代码:
// 声明一个长度为 5 的整型数组
var arr1 [5]int
// 声明一个长度为 3 的整型数组,并且赋值为[1, 2, 3]
var arr2 [3]int{1, 2, 3}
// 使用 ... 表示由编译器根据值来自行计算数组长度
var arr3 [...]int{0, 2, 4, 6, 8}
// 声明一个 4 行 5 列的二维整型数组
var arr4 [4][5]int
数组的大小也是类型的一部分
Go 语言中判断两个数组是否相等会比较两个部分,一是数组的长度是否相等,二是数组中存放的值顺序和大小是否相等。比如, [5]int 和 [10]int 就是两个不同的类型。
示例代码:
var a [2]int
var b = [2]int{1, 2}
var c = [...]int{1, 2}
fmt.Println("a == b ? ", a == b) //false
fmt.Println("b == c ?", b == c) //true
数组是值类型
Go 语言中数组是值类型而不是引用类型,这意味着当把一个数组赋值给一个新变量时,该变量会得到原始数组的一个副本,对新变量进行修改不会影响到原始数组。
示例代码:
a := [5]int{1, 3, 5, 7, 9}
b := a
b[0] = 100
fmt.Println(a) // 输出:1, 3, 5, 7, 9
fmt.Println(b) // 输出:100, 3, 5, 7, 9
数组的遍历
在 Go 语言中可以通过 len() 函数获取数组长度,然后使用 for 循环进行遍历;此外,还提供了一种更简洁的方式,通过 range 关键字来遍历,range 会返回索引和该索引的值。
示例代码:
arr := [...]int{2, 4, 6, 8}
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
for i, v := range arr {
fmt.Println(i, v)
}
切片(Slice)
Slice 的定义
Slice 是由数组创建的一种方便、灵活且功能强大的包装。Slice 本身没有数据,是对现有数组的引用。
Slice 在使用之前必须初始化,它没有零值。它的底层实现是一个指向数组的指针,在给它存入地址之前,它只能是 nil。
Slice 的声明方式与数组类似,写法上就像是一个没有长度的数组:var 切片名 []数据类型
数据类型也可以是 Slice,也就是 Slice 类型的 Slice,就构成了多维 Slice。
示例代码:
// 创建一个空的 Slice
var s1 []int
// 创建一个指向数组 [2, 4, 6, 8] 的 Slice
s2 := []int{2, 4, 6, 8}
// 通过内置函数 make() 创建一个长度 16 的 Slice
s3 := make([]int, 16)
// 创建一个长度 16,容量 32 的 Slice
s4 := make([]int, 16, 32)
// 从数组中创建 Slice, a[1:3] 表示从 a 中截取索引 1 到 4 的内容(左开右闭)
a := [...]int{1, 3, 5, 7, 9}
s5 := a[1:4] // s5 = [3, 5, 7]
Slice 的常用操作
Slice 自己不能拥有数据,是对底层数组的视图,对 Slice 进行的操作都会反映到原数组中。当一个数组有多个 Slice 时,所有 Slice 的更改都会反映在数组当中。
Slice 的长度是 Slice 中元素的个数,可以用函数 len() 获取,Slice 的容量是底层数组从创建 Slice 的索引开始到最后的元素个数,可以用函数 cap() 获取。
Slice 的长度是动态的,可以使用内置函数 append() 追加元素到尾部,如果 Slice 的长度超过了容量,会自动引用一个更大的数组来存放这些元素。
示例代码:
// arr 的长度为 100
arr := [100]int{0, 1, 2}
// 截取索引 2 到 6 创建 Slice
s1 := arr[2:6]
fmt.Println(len(s1),cap(s1)) //len(s1) == 4, cap(s1) == 98
// 追加元素
s1 = append(s, 11, 12)
// 复制
s2 := make([]int, 8)
copy(s2, s1)
fmt.Println(s2)
//从中间删除某一元素,Go 语言中没有对应的删除函数,使用 append() 也可以达到效果
s1 = append(s1[:4], s1[5:]...)
// 遍历方式和数组一致
for i := 0; i < len(s1); i++ {
fmt.Println(s1[i])
}
for i, v := range s1 {
fmt.Println(i, v)
}
字典(Map)
Map 的定义
Map 是 Go 语言中一种键(key)与值(value)关联的内置类型,具有无序、key 不可重复等特点。能够通过 key 快速找到 value,也被称为字典。
Map 的声明形式为:var 字典名 map[键类型]值类型
Map 之间不能使用 == 号进行判断,== 只能用来检查 Map 是否为 nil。
Map 的底层是一个指针,和变量不同,并不是声明后就可使用。与 Slice 相似,需要使用 make() 函数进行初始化,在初始化之前为空,没有零值。
示例代码:
// 仅声明,此时 map 不可用
var m1 map[string]string
fmt.Println(m1 == nil) // 输出:true
// 使用 make() 函数初始化
m2 := make(map[string]string)
fmt.Println(m2 == nil) // 输出:false
// 声明同时赋值
m3 := map[string]string{
"name": "aaa",
"course": "bbb",
"site": "ccc",
}
// 使用map[key]=value的形式对map进行赋值
m2["name"] = "aaa"
m2["course"] = "bbb"
Map 的常用操作
- 获取 map 中指定 key 的元素,语法为:map[key]
m := map[string]string{
"name": "aaa",
"course": "bbb",
"site": "ccc",
}
// 获取 map 中指定 key 的元素:map[key]
fmt.Println(m["name"])
// 当 map 中不存在这个 key 时,会返回空字符串
fmt.Println(m["abc"])
// 可以使用以下语法判断 key 是否存在
// 如果 key 存在,value 就是 key 所对应的元素,ok == true,否则 value 为零值,ok == false
value, ok := m["name"]
- 遍历 map 中的所有元素可以使用 for range 循环,与遍历数组和 Slice 用法一致。
m := map[string]string{
"name": "aaa",
"course": "bbb",
"site": "ccc",
}
for k, v := range m3 {
fmt.Println(k, v)
}
- 删除 map 中的元素,需要使用内置函数 delete(),该函数没有返回值。
m := map[string]string{
"name": "aaa",
"course": "bbb",
"site": "ccc",
}
delete(m, "name")
使用 Map 的一些注意事项
Map 是一个哈希表,key 必须可以比较相等,除了 Slice、map、function 之外的内建类型都可以作为 key,Struct 类型不包含这三种类型的字段,也可以作为 key。
Map 是线程不安全的,在多线程环境下,必须采取特殊的措施来保证 Map 的线程安全(比如使用sync包下的锁机制)。