数组
数组的声明
1 | var name [SIZE] type |
1 | var list [10] int |
注意:
- 数组长度必须是一个常量值
- 数组长度是数组类型的一部分,也就是说下面a,b两个数组是不同类型的
1 | var a [10] int |
数组初始化
方式一 指定数组长度初始化
1 | var list1 = [5] int {1,2,3,4,5} |
方式二 推断长度初始化
1 | var list1 = [...] int {1,2,3,4,5} |
多维数组的声明与初始化
方式一
1 | var list = [2][2] int { |
方式二
1 | var list = [...][2] int { |
注意
- 多维数组只有第一层可以使用
...
来让编译器推导数组长度。
数组的访问
与其他语言一样,数组可以通过下标来访问,例如array[index]
数组的遍历
在go语言中,数组有两种遍历方式,具体请看下面的demo
1 | package main |
输出
1 | 12246 |
数组是值类型
1 | package main |
输出
1 | 原数组[1 2 2 4 6] |
注意:
- 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
[n]*T
表示指针数组,*[n]T
表示数组指针 。
切片类型
切片的定义
Go语言的切片(slice)切片是一个拥有相同类型元素的可变长度的序列,类似于Java的list。我们知道,数组的大小是一个确定值,因此,在某些需要动态地扩展容器大小的地方,数组将变得不太适用。而切片在数组的基础上做了一层封装,以便支持需要动态扩容的场景。
Go语言中切片的内部结构包含底层数组的地址、切片的大小和切片的容量三个部分。
切片的声明
方式一
1 | // 声明一个int类型的切片 |
方式二 从数组中获取切片(初始化操作)
1 | package main |
输出
1 | 1234 |
方式三 通过make构造切片
1 | // capacity代表切片容量,这是一个可选参数 |
注意:
- 切片在未初始化之前是一个空切片(nil)
- 要检查切片是否为空,请始终使用
len(s) == 0
来判断,而不应该使用s == nil
来判断。 - 切片之间不能直接比较。切片唯一合法的比较操作是和
nil
比较。 一个nil
值的切片并没有底层数组,一个nil
值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil
举个例子:
1 | package main |
输出
1 | true |
切片表达式
上一小节当中的从数组获取切片,也是切片表达式的一种,下面介绍其他几种写法。
省略开始位置
1 | //这个数组a下面的例子中继续沿用 |
省略结束位置
1 | i := a[4:] |
开始、结束都省略
1 | i := a[:] |
提示: 切片表达式也可以用来操作切片,即上面的数组a也可以是一个切片。
len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
请看下方的demo
1 | package main |
输出
1 | 4 |
看到这里,大家或许会对切片容量和长度这两个值感到疑惑。上文咱讲切片的定义时曾说到,切片是对数组的封装,内部结构包含底层数组的地址、切片的大小和切片的容量三个部分。
切片的长度等于切片结束下标值减去切片开始下标值:
length = endIndex-startIndex
切片容量capacity等于底层数组长度减去开始下标值
capacity = len(array) - startIndex
所以上面的例子中的切片长度和容量分别是4和8。而底层数组的地址指向的就是切片开始下标对应值所在的内存地址。
1 | /**切片地层数组地址演示 |
输出
这也再一次证明了切片是引用类型。
切片的直接赋值和copy()函数
直接赋值
1 | package main |
注意
- 对切片i的操作将影响到切片j
举个例子
1 | package main |
输出
1 | 6 |
这不难理解,因为i、j都是一个保持对数组a的引用,因此,改变i的值实际上改变的是数组a的值,所以j也会受到影响。
copy()函数
1 | package main |
输出
1 | 3 |
显然,这里的i、j所对应的底层数组不是同一个。
append()函数的使用
为切片添加新元素
Go语言的内建函数append()
可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(切片参数后面加…)
1 | package main |
输出
1 | [1 2 4] |
移除元素
在go语言当中,目前并没有为切片移除元素的函数,因此我们使用append()来做移除操作。
1 | package main |
输出
1 | [5 7] |
提示
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()
函数调用时,所以我们通常都需要用原变量接收append函数的返回值。
切片的排序
对int类型的元素排序
1 | package main |
输出
1 | [6 7 9] |
对string类型的元素排序
1 | package main |
输出
1 | [lisi songjiu suiqi wangwu zhangsan zhaoliu] |
查找元素位置
1 | package main |
输出
1 | [张三 王五 李四 赵六 孙七 宋九] |
注意
- 查找之前需要先对切片进行排序
切片的扩容策略
以下源码摘自/src/runtime/slice.go
1 | func growslice(et *_type, old slice, cap int) slice { |
分析:
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap);
- 否则,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap);
- 否则,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap);
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
注意:
切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int
和string
类型的处理方式就不一样。