Go基础—2.容器
一、Go数组
数组的声明
var 数组变量名 [元素数量]Type
数组的简单操作
创建数组
var a [3]int = [3]int{1, 2, 3} // 初始化一个三个整数的数组
打印数组中的元素
fmt.Println(a[0]) // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素
// 打印数组中的所有索引和元素
for i, v := range a {
fmt.Printf("index: %d, value: %d\n", i, v)
}
// 只打印数组中的所有元素
for _, v := range a {
fmt.Printf("%d\n", v)
}
数组的赋值
var fruit [3]string
fruit[0] = "apple"
fruit[1] = "watermelon"
fruit[2] = "strawberry"
for index, name := range fruit {
fmt.Println(index, name)
}
/* 运行结果如下
0 apple
1 watermelon
2 strawberry
*/
数组需要注意的特性:
默认情况下,数组的每个元素都会被初始化为元素类型对应的零值,对于数字类型来说就是 0。例子如下所示:
var b [3]int = [3]int{1, 2}
fmt.Println(b[2])
/* 运行结果如下
0
*/
在数组的定义中,如果在数组长度的位置出现“…”省略号,则表示数组的长度是根据初始化值的个数来计算,所以我们初始化数组时可以写法如下:
c := [...]int{1, 2, 3}
数组的长度不同的情况下定义为两个类型不同的数组类型,比如[3]int和[4]int,比如我定义一个长度为3的数组,然后不能重新定义这个数组长度为4,例子如下所示:
d := [3]int{1, 2, 3}
d = [4]int{1, 2, 3, 4} // 报错编译错误:无法将 [4]int 赋给 [3]int
数组的比较
数组之间比较必须数组类型一致,哪怕数组数量不一样也不能比较,例子如下所示:
a := [2]int{1, 2}
b := [3]int{1, 2, 3}
c := [...]int{1, 2}
d := [3]int{1, 2}
fmt.Println(a == b, a == c, b == c)
/* 运行结果如下
true false false
*/
fmt.Println(b == d) // 编译错误,因为数组类型不一致,不能进行比较
数组切片
切片就是截取数组中的一段元素。
创建一个数组,然后再进行切片操作,例子如下所示:
var list_a [30]int
for i := 0; i < 30; i++ {
list_a[i] = i + 1
}
// 表示原有的切片
fmt.Println("原有的切片:", list_a[:])
// 清空所有拥有的元素
fmt.Println("清空后的切片:", list_a[0:0])
// 开头到中间的所有元素
fmt.Println("开头到中间的所有元素:", list_a[:5])
// 获取中间段元素
fmt.Println("获取中间段元素:", list_a[10:15])
// 中间到结尾的所有元素
fmt.Println("中间到结尾的所有元素:", list_a[20:])
// 输出切片的大小
fmt.Println("list_a切片大小为:", len(list_a))
/* 运行结果如下
原有的切片: [1 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]
清空后的切片: []
开头到中间的所有元素: [1 2 3 4 5]
获取中间段元素: [11 12 13 14 15]
中间到结尾的所有元素: [21 22 23 24 25 26 27 28 29 30]
list_a切片大小为: 30
*/
添加元素
append()方法可以为切片动态添加元素。
在切片的结尾添加元素
,具体代码如下所示:
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
注意:在使用append()函数动态添加元素时,空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。
在切片的开头添加元素
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
注意:在切片开头添加元素一般都会导致内存的重新分配,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。
添加元素后的容量测试
在切片进行扩容时,容量是按照容量的2倍进行扩充,比如:1、2、4、8、16…。
以下我们来打印出扩展后的容量来看下其中规律,代码如下所示:
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("id: %d len: %d cap: %d pointer: %p\n", i, len(numbers), cap(numbers), numbers)
}
/* 输出结果如下
id: 0 len: 1 cap: 1 pointer: 0x14000104020
id: 1 len: 2 cap: 2 pointer: 0x14000104040
id: 2 len: 3 cap: 4 pointer: 0x1400013a020
id: 3 len: 4 cap: 4 pointer: 0x1400013a020
id: 4 len: 5 cap: 8 pointer: 0x14000116080
id: 5 len: 6 cap: 8 pointer: 0x14000116080
id: 6 len: 7 cap: 8 pointer: 0x14000116080
id: 7 len: 8 cap: 8 pointer: 0x14000116080
id: 8 len: 9 cap: 16 pointer: 0x1400013c000
id: 9 len: 10 cap: 16 pointer: 0x1400013c000
*/
删除元素
Go语言并没有删除切片元素的专用语法,需要使用切片本身的特性来删除元素(截取需要的数重新赋值)。
从开头位置删除
移动数据指针方式:
a = []int{1, 2, 3, 4}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
数据指针不变方式:
使用append原地完成,就是在原有的切片数据对应的内存区间内完成,不会改变内存空间结构。
a = []int{1, 2, 3, 4}
// append()方法实现
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
// copy()方法实现
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
从中间位置删除
可以用 append 或 copy 原地完成。
a = []int{1, 2, 3, 4, ...}
// append()方法实现
a = append(a[:i], a[i+1:]...) // 删除中间1个元素,i代表元素的索引号
a = append(a[:i], a[i+N:]...) // 删除中间N个元素,i代表元素的索引号
// copy()方法实现
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素,i代表元素的索引号
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素,i代表元素的索引号
删除指定位置的元素。
【示例】代码如下所示:
package main
import "fmt"
func main() {
seq := []string{"a", "b", "c", "d", "e"}
// 指定删除位置
index := 2
// 查看删除位置之前的元素和之后的元素
fmt.Println("删除位置之前的元素:", seq[:index], "删除位置之后的元素:", seq[index+1:])
// 将删除点前后的元素连接起来
seq = append(seq[:index], seq[index+1:]...)
fmt.Println("删除指定位置元素之后的结果:", seq)
}
/* 运行结果如下
删除位置之前的元素: [a b] 删除位置之后的元素: [d e]
删除指定位置元素之后的结果: [a b d e]
*/
从尾部位置删除
跟从开头位置删除方法相似。
a = []int{1, 2, 3, 4}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)N] // 删除尾部N个元素
复制切片
Go语言中内置函数copy()可以复制数组切片,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。
copy()使用方法如下所示:
copy(destSlice, srcSlice)
copy() 函数的第一个参数是要复制的目标,第二个是源目标,两个数组可以共享同一个底层数组,甚至有重叠的部分也没有关系。
示例如下所示:
data1 := []int{1, 2, 3, 4, 5}
data2 := []int{6, 7, 8}
data3 := []int{5, 4, 3}
data4 := []int{0, 1, 2}
copy(data2, data1) //data1复制到data2切片中,只会复制data1的前3个元素到data2中
fmt.Println("复制之后的data2:", data2)
copy(data4, data3) //data3复制到data4切片中,只会复制data3的3个元素到data4的前3个位置
fmt.Println("复制之后的data4:", data4)
/* 运行结果如下
复制之后的data2: [1 2 3]
复制之后的data4: [5 4 3]
*/
下面示例演示了切片引用和复制操作,对切片元素数据的影响。
【示例】代码如下所示:
package main
import "fmt"
func main() {
// 设置元素数量为1000
const elementCount = 1000
// 创建源数组srcData并赋值0-999
srcData := make([]int, elementCount)
for i := 0; i < elementCount; i++ {
srcData[i] = i
}
// 初始refData变量引用切片srcData的数据
refData := srcData
// 创建复制数组copyData,并复制srcData的数据
copyData := make([]int, elementCount)
copy(copyData, srcData)
// 修改原始数据的第一个元素
srcData[0] = 999
// 打印引用切片的第一个元素
fmt.Println("引用的切片第一个元素:", refData[0])
// 打印复制切片的第一个和最后一个元素
fmt.Println("复制的切片第一个元素:", copyData[0], ",复制的切片最后一个元素:", copyData[elementCount-1])
// 复制原始数组切片从4到5的数据,并打印复制切片1到5位的元素
copy(copyData, srcData[4:6])
fmt.Printf("copyData从1到5位的值: ")
for i := 0; i < 5; i++ {
fmt.Printf("%d ", copyData[i])
}
}
最后得出,引用切片在改变源目标切片数据的同时会影响数据,复制切片则不会发生变化。
make()函数构造切片
如果需要动态创建一个切片,可以使用make()函数。
注意:size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。
make()方法如下:
make([]Type, size, cap)
示例如下所示:
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println("a:", a, "a的长度大小:", len(a))
fmt.Println("b:", b, "b的长度大小:", len(b))
/* 运行结果如下
a: [0 0] a的长度大小: 2
b: [0 0] b的长度大小: 2
*/
多维数组
多维数组的声明
var 数组变量名 [元素数量1][元素数量2]...[元素数量...]Type
以下例子来演示多维数组的运用。
【示例】代码如下所示:
// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var array [4][2]int
// 初始化一个二维整型数组的元素
array = [4][2]int{{1, 3}, {22, 14}, {29, 31}, {30, 41}}
fmt.Println("数组元素:", array)
// 声明并初始化数组中索引为 1 和 3 的元素
array = [4][2]int{1: {20, 21}, 3: {40, 41}}
fmt.Println("第1次更改:", array)
// 再次声明并初始化数组中指定的元素
array = [4][2]int{1: {0: 20}, 3: {1: 41}}
fmt.Println("第2次更改:", array)
/* 运行结果如下
数组元素: [[1 3] [22 14] [29 31] [30 41]]
第1次更改: [[0 0] [20 21] [0 0] [40 41]]
第2次更改: [[0 0] [20 0] [0 0] [0 41]]
*/
多维数组之间的赋值和取值操作。
【示例】代码如下所示:
var array [2][2]int
// 设置每个元素的整型值
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
array[1][1] = 40
fmt.Println("array的值:", array)
// 将 array1 的值复制给 array2
var array1 [2][2]int
var array2 [2][2]int
array1 = [2][2]int{{8, 17}, {16, 23}}
array2 = array1
fmt.Println("array2的值:", array2)
// 在多维数组中取单个维度的值,以及单个维度其中单个元素的值
var array3 [2]int = array1[1]
var value int = array1[1][0]
fmt.Println("array1[1]的值:", array3, "array1[1][0]的值:", value)
/* 运行结果如下
array的值: [[10 20] [30 40]]
array2的值: [[8 17] [16 23]]
array1[1]的值: [16 23] array1[1][0]的值: 16
*/
range关键字
range关键字的作用就是循环迭代切片,可以通过range去循环迭代切片中的每个元素。
通过range配合for循环去迭代切片中每个元素
【示例】代码如下所示:
// 创建一个切片并初始化赋值
var slice []int = []int{10, 2, 33, 42, 15}
// 迭代每一个元素,并输出索引号和值
for index, value := range slice {
fmt.Printf("索引序号: %d 元素的值: %d\n", index, value)
}
/* 运行结果如下
索引序号: 0 元素的值: 10
索引序号: 1 元素的值: 2
索引序号: 2 元素的值: 33
索引序号: 3 元素的值: 42
索引序号: 4 元素的值: 15
*/
二、Go映射(map)
Go语言中map是一种特殊的数据结构,一种元素对(pair)的无序集合,里面的数据对应着key(索引):value(值)这样的形式,这种结构也称为关联数组或字典。
map的声明语法,如下所示:
var map变量名 map[键类型]键对应的值类型
注意:在声明的时候不需要知道 map 的长度,因为 map 是可以动态增长的,未初始化的 map 的值是 nil
map简单操作
创建map
var a map[string]int
var b map[string]float32
c := make(map[string]string)
map赋值
// 对名为a的map进行赋值
a = map[string]int{"apple": 1, "orange": 2}
// 修改map对应key的值
a["apple"] = 3
a["orange"] = 2
打印map中的元素
// 打印整个map
fmt.Println(a)
fmt.Println(a["orange"])
/* 运行结果如下
map[apple:3 orange:2]
2
*/
map容量
和数组切片不同,map不存在固定长度或者最大限制,map可以根据key:value动态伸缩长度。但是可以标明map的初始容量capacity。
注意:当map增长到容量上限的时候,如果再增加新的key:value,map的大小会自动加1,所以出于性能的考虑,对于大的map或者会快速扩张的map,即使只是大概知道容量,也最好先标明。
示例如下所示:
// 语法格式
make(map[keytype]valuetype, cap)
// 示例如下
map := make(map[string]float, 100)
map中加入切片
map中可以使用切片当作value的值,我们只需要再定义map时通过将value的类型定义为[]int类型或者其他类型的切片就可以了。
示例如下所示:
mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)
循环遍历map
map 的遍历过程使用 for range 循环完成。
【示例】代码如下所示:
package main
import "fmt"
func main() {
var fruit map[string]int
fruit = map[string]int{"apple": 5, "orange": 2, "strawberry": 13, "watermelon": 6}
// 只循环输出key的值
for name := range fruit {
fmt.Println(name)
}
// 循环输出key和value的值
for name, price := range fruit {
fmt.Println(name, ", ", price)
}
// 将不需要的键使用"_"改为匿名变量形式。
for _, price := range fruit {
fmt.Println(price)
}
}
删除元素
Go语言提供了一个内置函数 delete(),用于删除容器内的元素。
语法如下:
delete(map名字, 键的值)
下面我们用一个示例来演示删除map中元素的方法。
【示例】代码如下所示:
// 创建一个map
var fruit map[string]int
fruit = map[string]int{"apple": 5, "orange": 2, "strawberry": 13, "watermelon": 6}
// 打印删除前的结果
fmt.Println("删除元素前:", fruit)
// 删除其中的元素
delete(fruit, "orange")
// 打印删除后的结果
fmt.Println("删除元素后:", fruit)
/* 运行结果如下
删除元素前: map[apple:5 orange:2 strawberry:13 watermelon:6]
删除元素后: map[apple:5 strawberry:13 watermelon:6]
*/
清空map中的所有元素
Go语言中并没有为map提供任何清空所有元素的函数、方法,清空map的唯一办法就是重新make一个新的map。