目录
一、Go语言切片介绍
1.1 什么是切片?
切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。
1.2 数组和切片的区别是什么?
切片和数组密不可分,如果将数组理解为一栋办公楼,那么切片就是把不同的连续楼层出租给使用者,出租的过程需要选择开始楼层和结束楼层,这个过程就会生成切片,数组和切片的区别如下:
- 定义方式不同:数组的长度是固定的,定义时需要指定长度,如
var arr [5]int;而切片的长度是可变的,定义时不需要指定长度,如var s []int。 - 内存分配方式不同:数组是在栈上分配内存,而切片是在堆上分配内存。因为切片的长度是可变的,所以需要在堆上分配内存,以便在运行时动态扩容。
- 传递方式不同:数组在函数传递时会进行值拷贝,而切片则是传递指针。因为切片是在堆上分配内存,所以传递指针可以避免拷贝整个切片,提高程序效率。
- 可操作性不同:数组是定长的,长度固定,不支持动态扩容,而切片可以动态扩容,支持append等操作。
二、声明切片
2.1 声明空的切片
语法如下:
var name []type
- name 表示切片的变量名
- type 表示切片对应的元素类型
声明空的切片案例如下:
package main
import "fmt"
func main() {
// 声明空的切片,类型为int
var intList []int
// 声明空的切片,类型为string
var strList []string
/* 声明空的切片,类型为int
本来会在{}中填充切片的初始化元素,这里没有填充,所以切片是空的,
但是此时的 intListEmpty 已经被分配了内存,只是还没有元素 */
var intListEmpty = []int{}
// 输出三个切片
fmt.Println("三个切片的值:", intList, strList, intListEmpty)
// 输出三个切片的大小
fmt.Println("三个切片的大小:", len(intList), len(strList), len(intListEmpty))
/* 切片判定空的结果
声明但未使用的切片的默认值是 nil,strList 和 numList 也是 nil,所以和 nil 比较的结果是 true
numListEmpty 已经被分配到了内存,但没有元素,因此和 nil 比较时是 false */
fmt.Println(intList == nil)
fmt.Println(strList == nil)
fmt.Println(intListEmpty == nil)
}
代码输出结果:
三个切片的值: [] [] []
三个切片的大小: 0 0 0
true
true
false
2.2 从数组指定范围生成切片
语法如下:
slice [开始位置 : 结束位置]
- slice:表示目标切片对象
- 开始位置:对应目标切片对象的索引
- 结束位置:对应目标切片的结束索引
案例如下:
package main
import "fmt"
func main() {
var slice [30]int
for i := 0; i < len(slice); i++ {
slice[i] = i
}
// 输出所有
fmt.Println(slice[:])
// 输出 1 - 6 元素对应的值
fmt.Println(slice[1:6])
// 输出 9 之后所有元素对应的值
fmt.Println(slice[9:])
// 输出 5 之前所有元素对应的值
fmt.Println(slice[:5])
}
代码输出结果:
[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29]
[1 2 3 4 5]
[9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29]
[0 1 2 3 4]
2.3 使用make()函数生成切片
make( []type, size, cap )
- type 切片的数据类型
- size 切片元素的个数
- cap 切片预分配元素的容量,可选
案例如下:
package main
import "fmt"
func main() {
slice1 := make([]int, 2)
slice2 := make([]int, 2, 10)
fmt.Println(slice1, slice2)
// 输出大小
fmt.Println(len(slice1), len(slice2))
// 输出cap
fmt.Println(cap(slice1), cap(slice2))
}
代码输出结果:
[0 0] [0 0]
2 2
2 10
其中 slice1 和 slice2 都是分配2个元素的切片,只不过slice2的内部存储空间已经分配了10个,但真实使用了2个元素。容量不会影响当前的元素个数,因此 len()的值都是2。
三、切片中添加元素
3.1 切片中添加元素
package main
import (
"fmt"
)
func main() {
var slice = make([]int, 2, 5)
slice[0] = 100
slice[1] = 200
fmt.Println(slice)
}
代码输出结果:
[100 200]
3.2 append()为切片添加元素
Go语言的内建函数 append() 可以为切片动态添加元素,代码如下所示:
package main
import "fmt"
func main() {
var slice []int
slice = append(slice, 1) // 追加1个元素
slice = append(slice, 2, 3, 4) // 最加多个元素
slice = append(slice, []int{5, 6, 7}...) // 追加一个切片
fmt.Println(slice)
}
代码输出结果:
[1 2 3 4 5 6 7]
注意:在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充,例如 1、2、4、8、16……,代码如下:
package main
import "fmt"
func main() {
var slice []int
for i := 0; i < 15; i++ {
slice = append(slice, i)
fmt.Printf("长度: %v 容量: %v 指针: %p\n", len(slice), cap(slice), slice)
}
}
代码执行结果:
长度: 1 容量: 1 指针: 0xc000018098
长度: 2 容量: 2 指针: 0xc0000180e0
长度: 3 容量: 4 指针: 0xc00000e1e0
长度: 4 容量: 4 指针: 0xc00000e1e0
长度: 5 容量: 8 指针: 0xc000014280
长度: 6 容量: 8 指针: 0xc000014280
长度: 7 容量: 8 指针: 0xc000014280
长度: 8 容量: 8 指针: 0xc000014280
长度: 9 容量: 16 指针: 0xc000020180
长度: 10 容量: 16 指针: 0xc000020180
长度: 11 容量: 16 指针: 0xc000020180
长度: 12 容量: 16 指针: 0xc000020180
长度: 13 容量: 16 指针: 0xc000020180
长度: 14 容量: 16 指针: 0xc000020180
长度: 15 容量: 16 指针: 0xc000020180
除了可以在切片尾部追加,也可以在切片前面添加 示例如下:
package main
import "fmt"
func main() {
var a = []int{1, 2, 3}
a = append([]int{666}, a...) // 前面添加1个元素
fmt.Println(a)
a = append([]int{777, 888, 999}, a...) // 前面添加多个元素
fmt.Println(a)
}
代码运行结果:
[666 1 2 3]
[777 888 999 666 1 2 3]
注意:在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。
3.3 copy()复制切片
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。copy() 函数的使用格式如下:
copy( destSlice, srcSlice []T) int
- srcSlice:数据来源切片
- destSlice:复制的目标
- 目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致
下面代码展示了copy()函数的过程
package main
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
}