数据类型篇(七):数组切片的创建和遍历

前一篇教程里我们已经介绍过数组的一个特点:数组的长度在定义之后无法修改,数组长度是数组类型本身的一部分,是数组的一个内置常量,因此我们无法在数组上做动态的元素增删操作。显然这种数据结构无法完全满足开发者的日常开发需求,尤其是从 PHP 转过来的开发人员(PHP 的数组非常灵活和强大),为此,Go 语言提供了数组切片(slice)来弥补数组的不足,数组切片一个最强大的功能就是支持对元素做动态增删操作,在介绍动态增删元素之前,我们先来了解下数组切片的定义和创建。

数组切片的定义

在 Go 语言中,数组切片是一个新的数据类型,与数组最大的不同在于,切片的类型字面量中只有元素的类型,没有长度:

var slice []string = []string{"a", "b", "c"}

数组和数组切片的区别

因此它是一个可变长度的、同一类型元素集合,切片的长度可以随着元素数量的增长而增长(不会随着元素数量的减少而减少),不过数组切片从底层管理上来看依然使用数组来管理元素,可以看作是对数组做了一层简单的封装。基于数组,数组切片添加了一系列管理功能,可以随时动态扩充存储空间,下面我们就来看看数组切片的创建和使用。

创建数组切片

创建数组切片的方法主要有三种 —— 基于数组、数组切片和直接创建,下面我们来简要介绍一下这几种方法。

基于数组

数组切片可以基于一个已存在的数组创建。从这个层面来说,数组可以看作是切片的底层数组,而切片则可以看作是数组某个连续片段的引用。数组切片可以只使用数组的一部分元素或者整个数组来创建,甚至可以创建一个比所基于的数组还要大的数组切片:

// 先定义一个数组
months := [...]string{"January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"}

  // 基于数组创建数组切片
  q2 := months[3:6]    // 第二季度
  summer := months[5:8]  // 夏季

  fmt.Println(q2)
  fmt.Println(summer)  

运行结果为:

[April May June]
[June July August]

Go 语言支持通过 array[first:last] 这样的方式来基于数组生成一个数组切片,而且这个用法还很灵活,比如下面几种用法都是合法的:

基于 months 的所有元素创建数组切片(全年):

all := months[:]

基于 months 的前 6 个元素创建数组切片(上半年):

firsthalf := months[:6]

基于从第 6 个元素开始的所有元素创建数组切片(下半年):

secondhalf := months[6:]

另外,通过这个示例,还可以探讨下数组切片底层的结构,数组切片底层引用了一个数组,由三个部分构成:指针、长度和容量,指针指向数组起始下标,长度对应切片中元素的个数,容量则是切片起始位置到底层数组结尾的位置,切片长度不能超过容量,比如上面的数组切片 q2,其指针指向底层数组 months 下标为 3 的位置,切片长度是3,切片容量是9(从下标3开始到下标11结束,可容纳9个元素),和数组一样,我们可以通过内置函数 lencap 来分别获取数组切片的长度和容量:

fmt.Println(len(q2))   // 3
fmt.Println(cap(q2))   // 9

基于数组切片

类似于数组切片可以基于一个数组创建,数组切片也可以基于另一个数组切片创建:

firsthalf := months[:6]
q1 := firsthalf[:3] // 基于firsthalf的前3个元素构建新数组切片

基于 firsthalf 创建数组切片时,选择的 firsthalf 元素范围可以超过所包含的元素个数,比如 q1 可以基于firsthalf 的前 9 个元素创建:

q1 := firsthalf[:9]

打印结果是:[January February March April May June July August September]

因为 firsthalf 的容量是 12,只要选择的范围不超过 firsthalf 的容量,那么这个创建操作就是合法的。

直接创建

并非一定要事先准备一个数组才能创建数组切片,Go 语言提供的内置函数 make() 可以用于灵活地创建数组切片。下面的例子示范了直接创建数组切片的各种方法:

// 创建一个初始元素个数为 5 的数组切片,元素类型为整型,初始值为 0,容量为 5
mySlice1 := make([]int, 5)

创建一个初始元素个数为 5 的整型数组切片,初始值为 [0 0 0 0 0],并预留 10 个元素的存储空间(容量为10):

mySlice2 := make([]int, 5, 10)

此外,还可以直接创建并初始化包含 5 个元素的数组切片:

mySlice3 := []int{1, 2, 3, 4, 5}

事实上,使用直接创建的方式来创建数组切片 Go 底层还是会有一个匿名数组被创建出来,然后调用基于数组创建切片的方式返回数组切片,只是上层不需要关心这个匿名数组的操作而已。

遍历数组切片

操作数组元素的所有方法都适用于数组切片,比如数组切片也可以按下标读写元素,用 len() 函数获取元素个数,并支持使用 range 关键字来快速遍历所有元素。

传统的元素遍历方法如下:

for i := 0; i < len(summer); i++ {
    fmt.Println("summer[", i, "] =", summer[i]) 
}

打印结果如下:

summer[ 0 ] = June
summer[ 1 ] = July
summer[ 2 ] = August

使用 range 关键字可以让遍历代码显得更简洁,range 表达式有两个返回值,第一个是索引,第二个是元素的值:

for i, v := range summer { 
    fmt.Println("summer[", i, "] =", v) 
}

两种方式打印结果完全一致。

下一篇我们将给大家介绍数组切片的一些高级使用,比如动态增减元素、内容复制等。

上一篇: 数据类型篇(六):数组及其使用

下一篇: 数据类型篇(八):在数组切片中动态增删元素