一、说明
切片 (slice) 是 golang 里面的可变长元素类型,不是数组或数组指针,本质是一个结构体。
slice 的声明如下 (位于安装目录下的 src/runtime/slice.go
):
|
type slice struct { array unsafe.Pointer //array 是一个指针,指向实际的数据地址。 len int //slice 的长度 cap int //slice 的容量 } |
值得注意的是,切片除了有一个 len
属性表明当前切片长度以外,还有一个容量标识 cap
,表示切片的最大容量,len
小于等于 cap
。查看长度和容量的方法很简单,直接使用 len()
和 cap()
即可。
二、创建方式
1. 直接创建
|
s := []int{1, 2, 3, 4, 5} //len(s)=5,cap(s)=5 |
也可以使用索引号指定
|
s := []int{1, 2, 3, 8:999} //len(s))=9, cap(s)=9, s:[1 2 3 0 0 0 0 0 999] |
2. 基于数组创建
通过给定数组的下标范围来创建切片,创建的时候,切片长度等于我们实际赋值的长度,容量默认等于数组第一个被赋值的元素
到最后一个元素
的距离:
|
data := [...]int{0, 1, 8, 3, 4, 5, 6, 7} s_data := data[1:5] //len(s_data)=4, cap(s_data)=7, s_data:[1, 8, 3, 4] |
这个距离也可以显式指定:
|
data := [...]int{0, 1, 8, 3, 4, 5, 6, 7} s_data := data[1:5:5] //len(s_data)=4, cap(s_data)=4, s_data:[1, 8, 3, 4] |
s_data := data[x:y:z]
的含义如下:
- x 表示赋值的起始索引,从 x 开始赋值。
- y 表示赋值的结束索引,长度就是 x-y 的距离。
- z 表示容量最大可以到的索引,x-z 的距离是容量。
通过数组创建的切片和数组共享同一块内存区域,修改切片的时候数组也会随之改变:
|
arr := [...]int{1, 2, 3, 4, 5} s := arr[:4] fmt.Println(s) // [1 2 3 4] s[1] = 99 fmt.Println(s[1], arr[1]) //99 99 |
3. 使用 make 创建
语法形式是 make([]Type, Len, Cap),
Type 是想要创建的切片类型,Len
是切片长度,Cap
是切片容量。
|
s := make([]int, 6, 8) fmt.Println(len(s), cap(s)) //6, 8 |
cap 不是必需的,可以省略不写,默认等于 len:
|
s := make([]int, 6) fmt.Println(len(s), cap(s)) //6, 6 |
三、使用示例
|
package main import "fmt" func main(){ s := []int{1, 2, 3, 4, 5, 6} //len(s))=9, cap(s)=9 fmt.Println(s[0], s[1]) //直接使用索引 fmt.Println(s[2:3]) //索引范围 [2, 3) fmt.Println(s[4:]) //[4, last] fmt.Println(s[:6]) //[start, 6) s2 := s[1:5] fmt.Println(s2) } |
输出:
|
1 2 [3] [5 6] [1 2 3 4 5 6] [2 3 4 5] |
四、和数组定义的区别
1. 数组的定义:
s := [3]int{1, 2, 3}
s := [...]int{1, 2, 3}
2. 切片的定义:
s := []int{1, 2, 3}
s := make([]s, 3)
五、注意事项
切片本质是一个结构体,使用时拷贝传递。在使用切片做函数形参时,会创建一个临时变量,但内部的数据还是不变。
|
package main import "fmt" func f(a []int){ fmt.Printf("%p %p %p ", &a, &a[0], &a[1]) } func main(){ s := []int{1, 2, 3, 4, 5, 6} fmt.Printf("%p %p %p ", &s, &s[0], &s[1]) f(s) } |
输出:
|
0xc04203e400 0xc04203bf50 0xc04203bf58 0xc04203e460 0xc04203bf50 0xc04203bf58 |
可以看到 s 的地址变了,但是内部 s[0]
, s[1]
的地址还是一样的,说明在创建副本的时候只是简单拷贝了切片的值,并没有把内部的内存空间重新分配。所以当我们使用 range
或者把切片作为函数形参时在代码块内部直接就会修改切片的值。
但是在要注意的是在把切片作为函数形参时,在函数内部使用 append
就不会对切片产生影响:
|
package main import ( "fmt" ) func f(a []string){ a = append(a, "hello") } func main(){ a := make([]string, 0, 4) f(a) fmt.Println(a) //输出 [],而不是 [hello] } |
原理很简单,因为 append
会返回一个新的 slice 给 a
,而 a
是 s
的一个副本,所以这里对 s
就不会有任何影响,此时要想修改 s
可以把切片改成指针类型:
|
package main import ( "fmt" ) func f(a *[]string){ *a = append(*a, "hello") } func main(){ a := make([]string, 0, 4) f(&a) fmt.Println(a) //[hello] } |
评论