以下文章原文来自公众号『太白技术』,欢迎关注,转载请联系公众号。
你好,我是太白,今天和你聊聊Go语言的切片(Slice)以及它的一些技巧。
1 前言
切片是Go语言中最多被使用的数据结构之一。本文将介绍切片的创建、操作、表达式以及使用的技巧。
2 数组
slice类型是建立在 Go 的数组类型之上的抽象,因此要理解 slice,我们必须首先理解数组。
先看一段代码:
var a [4]int // 数组类型: 定义指定长度和元素类型
a[0] = 1 // 数组a的第一个索引的值设置为1
i := a[0]
fmt.Println(i) // 输出:1, 说明已经设置成功
fmt.Println(a[1]) // 输出:0,说明数组不需要显式初始化,其元素本身为零
fmt.Println(a[4]) // 报错:invalid array index 4 (out of bounds for 4-element array)
(代码来自 Go Slices: usage and internals)
说明:
- 1、代码中
var a [4]int
完成了数组的初始化,表示一个由四个整数组成的数组。 - 2、默认值为0。当我们打印
a[1]
的时候输出0。 - 3、设置数组索引的值,可以直接指定下标的索引进行赋值,例子中是
a[0] = 1
。 - 3、Go数组的索引和其他语言一样,从0开始,数组的大小是固定的,上面例子中最大是4的的大小,所以下标索引最大是3,所以当我们访问
a[4]
会报数组越界的错误。
Go的数组是值类型
。不像C语言数组变量是指向第一个元素的指针,所以当我们把数组变量传递或者赋值的时候,其实是做copy的操作。比如下面的例子a赋值给b,修改a中的元素,并没有影响b中的元素:
a := [2]string{
"johnny", "太白技术"} // 长度`2`,可以改成`...`,编译器会自动计算数组的长度
b := a
a[0] = "xujiajun"
fmt.Println(a) // 输出:[xujiajun 太白技术]
fmt.Println(b) // 输出:[johnny 太白技术]
为了避免复制,你可以传递一个指向数组的指针,例如:
func double(arr *[3]int) {
for i, num := range *arr {
(*arr)[i] = num * 2
}
}
a := [3]int{
1, 2, 3}
double(&a)
fmt.Println(a) // [2 4 6]
(代码参考 Effective Go)
3 切片的创建
由于数组需要固定长度,很多时候不是很灵活,而切片更灵活、更强大。切片包含对底层数组的引用
,如果将一个切片分配给另一个切片,则两者都引用同一个数组。
切片底层的数据结构(src/runtime/slice.go):
type slice struct {
array unsafe.Pointer // 指针指向底层数组
len int // 切片长度
cap int // 底层数组容量
}
切片的类型规范是[]T,其中T是切片元素的类型。与数组类型不同,切片类型没有指定长度
。
创建切片有多种方式:变量声明
、切片字面量
、make创建
、new创建
、从切片/数组截取
。
1、变量声明
var s []byte
这种声明的切片变量,默认值是nil,容量和长度默认都是0。
var x []byte
fmt.Println(cap(x)) // 0
fmt.Println(len(x)) // 0
fmt.Println(x == nil) // true
2、切片字面量
当使用字面量来声明切片时,其语法与使用字面量声明数组非常相似:
a := []string{
"johnny", "太白技术"} // 这是切片声明,少了长度的指定
b := [2]string{
"johnny", "太白技术"} // 这是数组声明,多了长度的指定
3、make创建:
除了上面创建方式外,使用内置函数make
可以指定长度和容量来创建(具体函数:func make([]T, len, cap) []T)
其中 T 代表要创建的切片的元素类型。该make函数采用类型、长度和可选容量。调用时,make分配一个数组并返回一个引用该数组的切片。
首先看下空切片:
y := make([]int, 0)
fmt.Println(cap(y)) // 0
fmt.Println(len(y)) // 0
fmt.Println(y == nil) // false
下面这个例子中创建了长度为5,容量等于5的切片
var s []byte
s = make([]byte, 5, 5) // 指定了长度和容量
fmt.Println(s) // 输出:[0 0 0 0 0]
当容量参数被省略时,它默认为指定的长度:
s := make([]byte, 5)
fmt.Println(s) // 输出也是 [0 0 0 0 0]
内置len和cap函数检查切片的长度和容量:
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 5
4、使用new
使用new创建的切片是个nil切片。
n := *new([]int)
fmt.Println(n == nil) // true
5、从切片/数组截取
切片可以基于数组和切片来创建。这边会用到切片表达式,3.3会详细说明。
n := [5]int{
1, 2, 3, 4, 5}
n1 := n[1