go语言学习笔记(二)——内建容器篇

本篇主要记录对 go 中复杂的数据结构的学习,包括数组、切片、容器等。

数组

数组的声明

var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8}

var grid [4][5]int // 二维数组

数组的遍历:

go中数组遍历通常用以下两种方法:

方法一:类似于其他语言,如JavaScript的for循环

numbers := [6]int{1, 3, 2, 2, 5, 6}
for i := 0; i < len(numbers); i++ {
    fmt.PrintLn(numbers[i])
}

方法二:range

numbers := [6]int{1, 3, 2, 2, 5, 6}
maxi := -1
maxValue := -1
for i, v := range numbers {
    if v > maxValue {
        maxi, maxValue = i, v
    }
}

每次循环迭代,range产生一对值;索引以及在该索引处的元素值。

可能存在有时候不需要索引的情况,但range的语法要求, 要处理元素, 必须处理索引。一种思路是把索引赋值给一个临时变量, 如temp, 然后忽略它的值,但Go语言不允许使用无用的局部变量(local variables),因为这会导致编译错误。

Go语言中这种情况的解决方法是用空标识符(blank identifier),即_(也就是下划线)。空标识符可用于任何语法需要变量名但程序逻辑不需要的时候, 例如, 在循环里,丢弃不需要的循环索引, 保留元素值。

numbers := [6]int{1, 3, 2, 2, 5, 6}
sum := 0
for _, v := range numbers {
    sum += v
}

数组是值类型

[10]int 和[20]int 是不同类型;调用func f(arr [10]int) 会拷贝数组

切片

go语言中一般不直接使用数组,通常用切片(Slice)

slice本身是没有数据的,是对底层array的一个view,或是引用

slice的实现

先提一个问题:

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]

s1的值是?s2的值是?是否会报错?

s1 是 [2, 3, 4, 5],s2 是 [5, 6]

但是我们查看s1[4]的数据会报错,这是为什么?

这就要从slice的实现谈起,一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。

下图用图片简单说明上述描述:

1.png

问题也可以用下图进行拆分:

1.png

从上面描述也可以发现,slice是可以向后扩展的,但不可以向前扩展。即s[i]不可以超越len(s),通过slice向后扩展不可以超越底层数组cap(s)

再以s1和s2为例,len(s1) = 4,cap(s1) = 6,len(s2) = 2,cap(s2) = 3

slice - append

看下面的例子:

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)

s1,s2,s3, s4, s5的值分别为[2 3 4 5] [5 6] [5 6 10] [5 6 10 11] [5 6 10 11 12],arr则为[0 1 2 3 4 5 6 10]

为什么只有arr[7]的值改变了?之后两次append的值呢?

事实是,s4和s5底层所引用的数组并不是arr,而是新的数组。原因在于,添加元素时,如果超越了cap,那么系统会重新分配更大的底层数组。并且原来的数组会被拷贝过去。

此外,由于值传递的原因,必须接收append的返回值。因为在进行append时,slice的len会改变,ptr和cap也可能会改变(分配新底层数组的情况),因此必须要采用一个新的slice来接收append的返回值:s = append(s, val)

slice的创建

var s = []int

这种声明方式可以在没有array的基础上直接声明一个slice,但是此时s实际上是等于nil的,它的底层并没有分配数组。这之后可以对其进行append操作。

顺便可以进行一个有趣的实验:

var s []int

for i := 0; i < 100; i++ {
    fmt.Println(len(s), cap(s))
    s = append(s, i)
}
fmt.Println(s)

输出情况如下:

0 0
1 1
2 2
3 4
4 4
5 8
6 8
7 8
8 8
9 16
10 16
……
16 16
17 32
……
32 32
33 64
……
64 64
65 128
……
99 128
[0 1 2 3 4 5 6 7 8 9 …… 99]

可以看到,底部自动分配的数组长度是以2的次方进行递增的。

除了上面的 还有其他几种声明方法:

s1 := []int{2, 4, 6, 8}

s2 := make([]int, 16)

s2 := make([]int, 10, 32)

第一个增加了初始值,len和cap均为4。

后面两种使用到的make函数则可以规定len和cap值,且s2!=nil,其内部值均为初始零值。

相关方法

copy函数。

copy可以方便地将一个slice复制另一个相同类型的slice。copy函数的第一个参数是要复制的目标slice,第二个参数是源slice,目标和源的位置顺序和dst = src赋值语句是一致的。两个slice可以共享同一个底层数组,甚至有重叠也没有问题。copy函数将返回成功复制的元素的个数(我们这里没有用到),等于两个slice中较小的长度,所以我们不用担心覆盖会超出目标slice的范围。

删除

删除slice里的某个元素:比如要把s2里的第四个元素删除,后面的依次向前顶:

s2 = append(s2[:3], s2[4:]...)
pop
s = s[1:] // poping from front

s = s[:len(s) - 1] // poping from back

Map

map的声明

m := map[string]string {
    "name": "edison",
    "year": "18",
}

m2 := make(map[string]int) // m2 == empty map

var m3 map[string]int // m3 == nil

map的遍历

for k, v := range m {
    fmt.Println(k, v)
}

map是无序的,因此上述遍历是无法有序进行。

取值:

username := m["name"]

username := m["name231"] // 当取的key不存在时,拿到对应的zero value

username, ok := m["name"] // 如果对应key存在,ok为true,否则为false

可以看到,可以通过ok来确定是否有值,另外一种常用的方法如下:

if username, ok := m["name"], ok {
    fmt.Println(username)
} else {
    fmt.Println("key dose not exist")
}

删除

delete(m, "name")

长度

仍然可用len()来获得map的长度

map的key

  • map使用哈希表,必须可以比较相等
  • 除了slice,map,function的内建类型都可以作为key
  • Struct类型不包含上述字段,也可以作为key
 go语言学习笔记(三)——接口和多态篇
go语言学习笔记(一)——基础语法篇 
上一篇:go语言学习笔记(三)——接口和多态篇
下一篇:go语言学习笔记(一)——基础语法篇


如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信
QQ