1. 数组
学Go语言之前我已经多年没有看过数组了,唯一和数组打交道也是用Java的ArrayList,虽然底层是用数组实现的,但是全部的接口都是List的。
数组一度被我认为是一种落后的数据结构了,在我用Python写数据库小工具的时候也是喜欢用list的,我甚至不知道Python是否支持数组。
数组,在C语言中简直就是个魔鬼,因为数组和指针那么的浑然天成...
不过学习Go,很多资料都会着重的讲述数组,这是因为数组是切片和映射的基础数据结构,是很重要的东西。
数组,在Go语言中是一种长度固定的数据类型,而且在内存中是连续分配的,检索速度也是很快的。
//最初学习C语言的时候,数组总是这样声明
var array [5]int
//还有这种声明方式
var array = [5]int{1,2,3,4,5}
//Go语言还能自动推断
array := [5]int{1,2,3,4,5}
//当然我不想数有多少个元素
array =: [...]int{1,2,3,4,5}
这几种都是Array常见的声明方式。如果没有初始化赋值,数组每一个元素都会被初始化为其类型的0值,在这个例子里,第一种声明方式就会变成下图这样:
多像一只毛毛虫啊。Go还支持一种新奇的初始化方式:
//这种方式指定了几个index的值,其余的都是其类型的0值
array := [5]int{1, 10, 2:20}
Go数组的使用和C这种语言也没有什么区别,index也是从0开始。
2. 切片
用惯了ArrayList之后,感觉这种固定长度的数组就是不爽,很多时候我需要写一个循环,把循环的到的结果写入到List里,这个时候是绝对不能用固定长度数据结构的。
还好Go语言有切片这个数据结构。可以通过make函数或者字面量的形式创建切片,这两种方式都没有问题:
//创建一个长度和容量为5的切片
s1 := make([]int, 5)
//创建一个长度为5,容量为8的切片
s2 := make([]int, 5, 8)
这里引入了一个概念,切片的容量和长度。这是什么东西其实还要从底层的实现说起,刚才说了,数组是切片的底层数据结构:
其实,执行make函数创建一个长度为5,容量为8的切片就是做了这样一件事情,即创建了一个长度为8的数组,并且将指针指向了这个数组。
还要说明一点,如果创建了一个长度为5,容量为8的切片,那么直接打印这个切片,得到的一定是五个零:
package main
import "fmt"
func main() {
// 示例 1。
s1 := make([]int, 5, 8)
fmt.Println(s1)
}
切片的操作也和数组一样,一切操作基于index,且index从0开始。
好玩的一点是,切片也是可以切分的:
package main
import "fmt"
func main() {
// 示例 1。
s1 := []int{1,2,3,4,5,6,7,8}
s2 := s1[3: 6]
//猜猜这里会输出什么?
fmt.Println(s2)
fmt.Println(cap(s2))
fmt.Println(len(s2))
}
s1[4:7]代表着从index=3的地方开始切分,一直切刀index=6-1的地方,结果就是{4, 5, 6}。那么很明显,这个新的切片长度一定是3,但是容量呢?
容量是5。
这还是要从底层说起:
虽然截取的是绿色的框,但是因为底层的数组实现,其实还是一个数组,因此,计算容量的时候,要把截取窗口右侧的元素也算上。
总结一个定理:
如果对底层数组是k的切片进行[i: j]的切分,其长度是j-i,其容量是k-i。
这个例子里,长度是7-4即3,容量是8-3即5。
切片既然长度是不定的,那么这个容量还有什么用处呢?
回忆一下Java的ArrayList就知道了,ArrayList底层也是使用数组实现,且初始化的时候会直接分配一个初始化长度的数组,当这个数组被填满的时候(这个时候可以认为容量不足),就会新分配一个数组。
切片也不例外,这么好的策略是放之四海而皆准的。当底层数组容量不够的时候,系统会分配给切片一个新的底层数组。这里有一个增长因子的概念,当切片元素超过1000个,则每次增加25%的容量。
3. 小结
用Python写小工具的时候,类型一直是个问题,有时候数据结构太多也是个问题,比如元组,列表,字典。有点乱花渐欲迷人眼的感觉。Go常用切片,数组和映射,相对简单很多,还能通过struct自己构建类型。
逐渐有了一种把原来写的Python数据库小工具改写成Go的冲动。毕竟Go是可以直接打包成可执行文件的,不需要考虑依赖,这对于没有办法连网的环境,是很可贵的。