golang复合类型——切片
数组与切片
数组
所谓数组,即内存上一块连续的存储空间。使用方式
// 声明数组
var nums [3]int
// 数组赋值
nums[0] = 1
nums[1] = 2
nums[2] = 3
// 输出数组元素
for i:=0; i<3; i++ {
fmt.Println(nums[i]) //1、2、3
}
我们可以利用数组批量处理数据,看似很方便,但数组在实际的工程项目中是很鸡肋的,因为它无法动态改变长度。
比如我现在想再加一个数字“4”,如果你这样做
nums[3] = 4
这段代码编译的时候会直接报“数组越界”的错误,因为数组的长度是在声明的时候就已经决定好的,声明后就不能再改变数组的长度了。
invalid argument: index 3 out of bounds [0:3]
从内存管理的角度来看,当你声明nums数组的时候,内存会直接分配一段固定的连续空间,也就是长度是无法再修改的了。假如这段空间不固定,我们可以继续在其后面存储数据,那这段代码有可能导致其他应用崩溃,因为它有可能覆盖掉其他应用的内存数据,这是十分危险的行为。
那么我们该怎么把4放入nums中呢?重新声明一个长度为4的数组
// 声明一个长度为10的数组
var newNums [10]int
// 将nums的元素拷贝进newNums中
for i:=0; i<3; i++ {
newNums[i] = nums[i]
}
// 把4放入newNums中
newNums[3] = 4
// 输出数组元素
for i:=0; i<4; i++ {
fmt.Println(newNums[i]) //1、2、3、4
}
上面的代码我们先重新声明了一个长度为4的数组newNums,然后将nums的元素放入到newNums,最后将4放入newNums。这个方法其实相当于我们手动将nums扩容。
虽然我们现在实现了手动扩容数组,但是还有一个问题,如果后续我们要加入5、6、7、8、9...总不能每次都写这么一段代码吧,而且频繁的分配内存也是不可取的。就像你去银行取钱,你预计这个月要支出30万,但单天只需要1万,你总不能一天取一万,这个月每天都跑一趟吧,其时间成本是非常高的。最好的办法其实就是一次多取一些钱,当然,也不一定要一次性全取出,因为30万是预计金额,所以是有够花和不够花两种结果的。那么,我们代码再改一下
// 声明一个长度为4的数组
var newNums [10]int
// 将nums的元素拷贝进newNums中
for i:=0; i<3; i++ {
newNums[i] = nums[i]
}
// 把4放入newNums中
newNums[3] = 4
// 输出数组元素
for i:=0; i<4; i++ {
fmt.Println(newNums[i]) //1、2、3、4
}
newNums[4] = 5
newNums[5] = 6
newNums[6] = 7
newNums[7] = 8
// 输出数组元素
for i:=0; i<8; i++ {
fmt.Println(newNums[i]) //1、2、3、4
}
Ok,现在数组使用起来是不是方便了一些?但我们不能每使用一个数组都来这么一套,所以我们需要对这个过程进行封装。当然,这个过程已经被go的官方封装好了,也就是我用的最多的复合数据类型——切片。
切片
我们先看一下切片的结构:
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
array 是底层数组,用来存储元素;len 表示切片当前已使用的长度; cap 则代表切片容量。
创建切片
// 方式一:使用 make 关键字
s1 := make([]int, 2)
s2 := make([]int, 2, 4)
// 方式二:使用 array[low : high : max]
a1 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s3 := a1[2:5:7] // len = high - low, cap = max - low, s3 = [3, 4, 5, 0, 0]
这里我们重点讲解一下方式二,它里面存在很多坑点。
切片注意事项
- 区分 var s1 []int 和 s2 := make([]int, 0)
s1 是 nil切片,没有底层数组Data,其len=0、cap=0;
s2是空切片,它不但分配了 24 字节的头部结构,它的 Data 指针也是有值的(指向一个特殊的全局零尺寸内存地址),尽管它的长度和容量也是 0。

浙公网安备 33010602011771号