Go语言切片详解
切片是围绕动态数组的概念构建的,可以按需自动增长和缩小,总的来说,切片可理解为动态数组,并根据切片中的元素自动调整切片长度。
切片的定义和使用
切片是动态数组,可以根据切片中的元素自动调整切片长度,切片的定义方式与数组定义十分相似,但定义过程中不用设置切片长度,其语法格式如下:// 定义切片 var name []type // 定义切片并赋值 var name = []type{value1, value2} // 简写 name := []type{value1, value2} // 使用make()定义切片 var name []type = make([]type, len) // 简写 name := make([]type, len)切片的定义语法说明如下:
- name 定义切片及使用时的变量名;
- type 设置切片元素的数据类型;
- value 为切片中的某个元素值;
- make 是内置函数方法,它为切片、集合和通道分配内存和初始化;
- len 设置切片长度,切片长度等于切片元素个数。
定义切片一共划分为3种方式:只定义、定义并赋值、使用 make() 函数定义,它们的使用方式如下:
package main import "fmt" func main() { var s []int var ss = []int{1, 2} var sss []int = make([]int, 3) fmt.Printf("只定义:%v,内存地址为:%v\n", s, &s) fmt.Printf("定义并赋值:%v,内存地址为:%v\n", ss, &ss) fmt.Printf("使用make()函数定义:%v,内存地址为:%v\n", sss, &sss) }不同定义方式使切片的元素值各有不同,具体说明如下:
- 只定义切片只生成一个空切片,切片中没有任何元素。
- 定义并赋值根据元素个数设置切片长度,每一个值将作为切片的一个元素。
- 使用 make() 函数定义必须设置参数 len,该参数是设置切片长度,并且切片的每个元素将会设置相应的默认值。
- 由于切片支持动态变化,因此 Go 语言不会为切片分配内存地址。
运行上述代码,运行结果为:
只定义:[],内存地址为:&[]
定义并赋值:[1 2],内存地址为:&[1 2]
使用make()函数定义:[0 0 0],内存地址为:&[0 0 0]
如果定义的切片不是空切片,使用切片的索引下标修改切片中已有的元素值,比如切片 ss =[]int{1, 2},修改第一个元素值的实现代码如下:
package main import "fmt" func main() { var ss = []int{1, 2} fmt.Printf("切片变量ss的元素值为:%v\n", ss) // 修改第一个元素值 ss[0] = 100 fmt.Printf("切片变量ss的元素值为:%v\n", ss) }如果索引下标的数值大于切片长度,那么程序无法编译成功,比如 ss[10]=100,程序将提示索引超出范围,如图 1 所示。
图 1 异常信息
新增切片元素
由于切片是动态数组,即使在定义的时候设置了切片长度,我们还能向切片添加新的元素。在切片中新增元素必须使用内置函数方法 append() 实现,它通常需要设置两个参数,语法格式如下:ss := append(slice, elems)内置函数方法 append() 说明如下:
- 参数 slice 代表待新增元素的切片;
- 参数 elems 代表新增元素的元素值,其数据类型必须与切片的数据类型相同;
- ss 是内置函数方法 append() 的返回值,代表新增元素后的切片。
使用 append() 对切片新增元素,如果函数返回值(称为切片变量 B)与原有切片变量 A 的命名相同,那么新增元素后的切片变量 B 将覆盖原有切片变量 A。
如果原有切片变量 A 与新增元素后的切片变量 B 的命名不相同,那么新增元素后的切片变量 B 与原有切片变量 A 是两个独立的切片变量,示例如下:
package main import "fmt" func main() { var ss = []int{1, 2} fmt.Printf("新增元素前的切片ss:%v\n", ss) // 新增元素不覆盖原有切片 sss := append(ss, 3) fmt.Printf("新增元素后的切片ss:%v\n", ss) fmt.Printf("新切片sss:%v\n", sss) // 新增元素并覆盖原有切片 ss = append(ss, 4) fmt.Printf("新增元素后的切片ss:%v\n", ss) // 添加多个元素 ss = append(ss, 5, 6, 7, 8) fmt.Printf("新增元素后的切片ss:%v\n", ss) }运行上述代码,运行结果为:
新增元素前的切片ss:[1 2]
新增元素后的切片ss:[1 2]
新切片sss:[1 2 3]
新增元素后的切片ss:[1 2 4]
新增元素后的切片ss:[1 2 4 5 6 7 8]
内置函数方法 append() 还可以实现两个切片之间的拼接,只要 append() 的参数 elems 与参数 slice 是相同数据类型的切片即可实现拼接,示例代码如下:
package main import "fmt" func main() { var s1 = []int{1, 2, 3} var s2 = []int{4, 5, 6, 7} ss := append(s1, s2...) fmt.Printf("切片变量s1:%v\n", s1) fmt.Printf("切片变量s2:%v\n", s2) fmt.Printf("切片变量ss:%v\n", ss) }实现两个数据类型相同的切片拼接,参数 elems 将作为其中一个切片变量,必须在切片变量后面添加“…”,这是对切片进行解包处理,如上述代码的切片变量 s2…,它是将切片变量 s2 的元素批量添加到切片 s1 中。
如果不对切片变量 s2 执行解包处理,程序就无法实现切片拼接,并提示异常信息。最后运行上述代码,运行结果为:
切片变量s1:[1 2 3]
切片变量s2:[4 5 6 7]
切片变量ss:[1 2 3 4 5 6 7]
截取切片元素
如果需要截取切片的部分元素,截取方式是使用索引下标进行定位和截取,截取语法如下:s := slice[startIndex: endIndex]截取切片的语法说明如下:
- slice 代表需要被截取的切片;
- s 代表已完成截取的切片;
- startIndex 是开始截取的元素位置;
- endIndex 是结束截取的元素位置。
如果截取后的切片变量与截取前的切片变量命名相同,那么截取后的切片变量会覆盖截取前的切片变量;如果两者命名不相同,程序默认设置为两个独立的切片变量。
截取的起始位置和终止位置可以根据实际需要进行设定,并不强制要求,如果没有设置起始位置或终止位置,程序默认为第一个或最后一个元素的位置,示例如下:
package main import "fmt" func main() { var ss = []int{1, 2, 3, 4, 5, 6, 7} // 截取第二个到第五个元素 s1 := ss[1:4] fmt.Printf("截取第二个到第五个元素:%v\n", s1) // 截取第三个元素之后的所有元素 s2 := ss[2:] fmt.Printf("截取第三个元素之后的所有元素:%v\n", s2) // 截取第三个元素之前的所有元素 s3 := ss[:2] fmt.Printf("截取第三个元素之前的所有元素:%v\n", s3) // 如果切片ss没被覆盖,经过截取后不改变原有的切片数据 fmt.Printf("切片变量ss的值:%v\n", ss) }上述代码演示了切片常用的截取方式,分别为:截取切片的固定元素、截取某个元素前面的所有元素、截取某个元素后面的所有元素,具体说明如下:
- 截取切片的固定元素必须设置元素的起始位置和终止位置,并且起始位置的值小于或等于终止位置的值。如果起始位置等于终止位置,截取结果为空切片;
- 截取某个元素前面的所有元素只需设置截取元素的终止位置,起始位置默认为切片的第一个元素;
- 截取某个元素后面的所有元素需设置截取元素的起始位置,终止位置默认为切片的最后一个元素。
运行上述代码,运行结果为:
截取第二个到第五个元素:[2 3 4]
截取第三个元素之后的所有元素:[3 4 5 6 7]
截取第三个元素之前的所有元素:[1 2]
切片变量ss的值:[1 2 3 4 5 6 7]
若要删除切片变量的部分元素,首先使用切片截取,过滤掉不需要的切片元素,保留需要的切片元素,然后使用内置函数方法 append() 将两个或多个已保留的切片元素进行拼接。
例如,去掉切片 ss = []int{1, 2, 3, 4, 5, 6, 7} 的元素 4、5、6,实现代码如下:
package main import "fmt" func main() { var ss = []int{1, 2, 3, 4, 5, 6, 7} fmt.Printf("切片ss的元素:%v\n", ss) // 删除元素4、5、6,先截取后拼接 ss = append(ss[:2], ss[6:]...) fmt.Printf("切片ss的元素:%v\n", ss) }运行上述代码,运行结果为:
切片ss的元素:[1 2 3 4 5 6 7]
切片ss的元素:[1 2 7]
复制切片
Go 语言的内置函数方法 copy() 可以将一个切片(数组)复制到另一个切片(数组)中,并且两个切片的数据类型必须相同。如果两个切片(数组)的长度不同,程序按照待复制的切片元素的个数进行复制。具体语法格式如下:
i := copy(slice1, slice2)内置函数方法 copy() 的说明如下:
- slice1 代表待复制的切片;
- slice2 代表被复制的切片;
- i 代表内置函数方法 copy() 的返回值,代表复制的元素数量。
下面通过代码示例说明内置函数方法 copy() 的使用方法,代码如下:
package main import "fmt" func main() { slice1 := []int{1, 2, 3} slice2 := []int{4, 5, 6} // 将slice1的元素复制到slice2 //copy(slice2, slice1) // 将slice2的元素复制到slice1 copy(slice1, slice2) fmt.Printf("将slice2的元素复制到slice1:%v\n", slice1) fmt.Printf("将slice2的元素复制到slice1:%v\n", slice2) slice3 := []int{7, 8, 9, 10} // 将slice3的元素复制到slice1 //copy(slice1, slice3) //fmt.Printf("将slice3的元素复制到slice1:%v\n", slice1) //fmt.Printf("将slice3的元素复制到slice1:%v\n", slice3) // 将slice1的元素复制到slice3 copy(slice3, slice1) fmt.Printf("将slice1的元素复制到slice3:%v\n", slice1) fmt.Printf("将slice1的元素复制到slice3:%v\n", slice3) }运行上述代码,运行结果为:
将slice2的元素复制到slice1:[4 5 6]
将slice2的元素复制到slice1:[4 5 6]
将slice1的元素复制到slice3:[4 5 6]
将slice1的元素复制到slice3:[4 5 6 10]
- 内置函数方法 copy() 第一个参数 slice1 的切片元素将会被第二个参数 slice2 的切片元素覆盖,第二个参数 slice2 的切片元素保持不变。
- 如果参数 slice1 和参数 slice2 的切片元素个数不同,复制过程以参数 slice1 的切片元素个数为主。
- 如果参数 slice1 的切片元素个数大于参数 slice2,则参数 slice2 的所有切片元素依次替换参数 slice1 的切片元素,参数 slice1 没有替换的切片元素保存不变。
- 如果参数 slice1 的切片元素个数小于参数 slice2,则参数 slice2 的切片元素依次替换参数 slice1 的切片元素,直到参数 slice1 的所有元素被替换为止,参数 slice2 剩余的切片元素不参与复制过程。
切片长度与容量
使用内置函数方法 make() 定义切片的时候,必须设置切片长度(也称为切片大小),但内置函数方法 make() 还有一个可选参数 cap,它用于设置切片容量,默认情况下,切片长度与容量是相同的。为了更好地讲述切片长度与容量之间的关系,我们通过示例加以说明:package main import "fmt" func main() { // 内置函数方法cap()获取切片容量 // 内置函数方法len()获取切片长度 s1 := make([]int, 3, 4) fmt.Printf("切片变量s1的值:%v\n", s1) fmt.Printf("切片变量s1的长度:%v\n", len(s1)) fmt.Printf("切片变量s1的容量:%v\n", cap(s1)) // 第一次添加元素 s1 = append(s1, 10) fmt.Printf("切片变量s1的值:%v\n", s1) fmt.Printf("切片变量s1的长度:%v\n", len(s1)) fmt.Printf("切片变量s1的容量:%v\n", cap(s1)) // 第二次添加元素 s1 = append(s1, 10) fmt.Printf("切片变量s1的值:%v\n", s1) fmt.Printf("切片变量s1的长度:%v\n", len(s1)) fmt.Printf("切片变量s1的容量:%v\n", cap(s1)) }运行上述代码,运行结果为:
切片变量s1的值:[0 0 0]
切片变量s1的长度:3
切片变量s1的容量:4
切片变量s1的值:[0 0 0 10]
切片变量s1的长度:4
切片变量s1的容量:4
切片变量s1的值:[0 0 0 10 10]
切片变量s1的长度:5
切片变量s1的容量:8
- 首先定义长度为 3、容量为 4 的切片变量 s1,并且切片前3个元素的默认值为 0。
- 使用内置函数方法 append() 往切片变量 s1 中添加数值 10,切片变量 s1 的长度变为 4,容量保持不变。
- 再一次使用内置函数方法 append() 往切片变量 s1 中添加数值 10,切片变量 s1 的长度变为 5,容量翻倍增长,增长为 8。
综上分析,当切片长度大于容量的时候,Go 语言将原有容量扩大至两倍,否则元素无法新增到切片中。换句话说,把切片容量比作停车场的停车位,每个停车位只能停一辆车,把切片长度比作停车场的车辆数量,当所有停车位停满的时候,再有一辆车进入停车场,停车场就无法提供车位,只能向外扩展开发更多停车位。
同理,当切片容量等于长度的时候,程序无法存放新的元素,只能扩大切片容量,为新元素提供足够的存储空间。