一、说明
切片(slice)是golang里面的可变长元素类型,不是数组或数组指针,本质是一个结构体。
slice的声明如下(位于安装目录下的src/runtime/slice.go
):
1 2 3 4 5 |
type slice struct { array unsafe.Pointer //array是一个指针,指向实际的数据地址。 len int //slice的长度 cap int //slice的容量 } |
值得注意的是,切片除了有一个len
属性表明当前切片长度以外,还有一个容量标识cap
,表示切片的最大容量,len
小于等于cap
。查看长度和容量的方法很简单,直接使用len()
和cap()
即可。
二、创建方式
1.直接创建
1 |
s := []int{1, 2, 3, 4, 5} //len(s)=5,cap(s)=5 |
也可以使用索引号指定
1 |
s := []int{1, 2, 3, 8:999} //len(s))=9, cap(s)=9, s:[1 2 3 0 0 0 0 0 999] |
2.基于数组创建
通过给定数组的下标范围来创建切片,创建的时候,切片长度等于我们实际赋值的长度,容量默认等于数组第一个被赋值的元素
到最后一个元素
的距离:
1 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] |
这个距离也可以显式指定:
1 2 |
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的距离是容量。
通过数组创建的切片和数组共享同一块内存区域,修改切片的时候数组也会随之改变:
1 2 3 4 5 |
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
是切片容量。
1 2 |
s := make([]int, 6, 8) fmt.Println(len(s), cap(s)) //6, 8 |
cap不是必需的,可以省略不写,默认等于len:
1 2 |
s := make([]int, 6) fmt.Println(len(s), cap(s)) //6, 6 |
三、使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 4 5 |
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)
五、注意事项
切片本质是一个结构体,使用时拷贝传递。在使用切片做函数形参时,会创建一个临时变量,但内部的数据还是不变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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) } |
输出:
1 2 |
0xc04203e400 0xc04203bf50 0xc04203bf58 0xc04203e460 0xc04203bf50 0xc04203bf58 |
可以看到s的地址变了,但是内部s[0]
, s[1]
的地址还是一样的,说明在创建副本的时候只是简单拷贝了切片的值,并没有把内部的内存空间重新分配。所以当我们使用range
或者把切片作为函数形参时在代码块内部直接就会修改切片的值。
但是在要注意的是在把切片作为函数形参时,在函数内部使用append
就不会对切片产生影响:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
可以把切片改成指针类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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] } |
评论