slice英文切片的意思,给黄瓜切片,给面包切片。这里的slice是给数组切片,截取数组的一部分。go语言的slice不但是一个动词,而且是一个名词,既一种数据结构类似于数组的数据结构,甚至底层就是用数组实现的。
数组所有的优点slice都有,而且slice更加灵活,slice支持可以通过append向slice中追加元素,长度不够时会动态扩展,通过再次slice切片,可以得到得到更小的slice结构,可以迭代、遍历。
数组是一组数据类型相同,连续且长度固定的数据项序列,slice底层也是一个数组,但是slice维护了一个指针属性,指向创建的数组。
slice底层是数组,它的数据结构如下:
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 数组指针
len int // 长度
cap int // 最大容量
}
slice保存了底层数组的地址,数组的长度,以及最大容量。
第一种定义方式:
var var_name []var_type
切片不需要指定长度,未初始化之前指针默认为 nil,长度为 0,最大容量为0。我们可以通过append函数追加元素,而不是像数组那样长度固定,元素数量不能改变。
package main
import (
"fmt"
)
func main() {
var num []int
if num == nil {
fmt.Println(len(num), cap(num))
} //输出 0 0
num = append(num, 1)
fmt.Println(len(num), cap(num))
//输出 1 1
}
第二种定义方式:
var_name []type = make([]type, len) 或者 var_name := make([]type, len)
另外还可以指定容量,其中 capacity 为可选参数,var_name []type = make([]type, len, capacity),如果不设定capacity,默认capacity等于len。
var num = make([]int, 3, 5)
结构如下:
我们可通过append添加元素,当元素数量超过最大容量时,会进行扩容,扩容到最大容量的1倍,如何扩容呢,就是找一块2倍的capacity的内存,将原来的数据拷贝过去,然后释放原来的内存。这个跟C++中的vector很像。
我们可以通过一个例子看出来:
package main
import (
"fmt"
"unsafe"
)
func main() {
var num = make([]int, 3, 5)
//打印底层数组首地址
fmt.Println(unsafe.Pointer(&num[0]))
num = append(num, 0)
//地址没有变
fmt.Println(unsafe.Pointer(&num[0]))
//地址仍然没有变
num = append(num, 3)
fmt.Println(unsafe.Pointer(&num[0]))
num = append(num, 4)
//地址发生变化
fmt.Println(unsafe.Pointer(&num[0]))
fmt.Println(num)
//输出 6 10
fmt.Println(len(num),cap(num))
}
这个例子说明append的时候,只要没有超过最大容量,返回的slice的指针不变,capacity不变,底层数组的内存不变。
如果append超出最大容量,但是用一个新的slice去接返回值,原来的slice不会销毁,新的slice指向新分配的底层数组。
package main
import (
"fmt"
"unsafe"
)
func main() {
var num = make([]int, 3, 3)
//打印底层数组首地址
fmt.Println(unsafe.Pointer(&num[0]))
num1 := append(num, 0)
fmt.Println(unsafe.Pointer(&num[0]))
fmt.Println(unsafe.Pointer(&num1[0]))
fmt.Println(num)
fmt.Println(num1)
}
slice可以缩小,就是切片,截取slice的一部分:
SLICE
[index1:index2]
index是slice的下标,就是截取从index1到index2之间的元素,左闭右开
我们还可以指定slice的容量:
SLICE
[index1:index2:index3]
如果不指定就是index1到底部的长度为新的slice的容量。
package main
import (
"fmt"
"unsafe"
)
func main() {
var num = make([]int, 3, 5)
nnum := num[1:3]
//输出4 2
fmt.Println("nnum:", cap(nnum), len(nnum))
s := []int{1, 2, 3, 4}
//s: 4 4
fmt.Println("s:", cap(s), len(s))
ns := s[1:3]
//ns: 3 2
fmt.Println("ns:", cap(ns), len(ns))
//输出地址一样
fmt.Println(unsafe.Pointer(&s[1]))
fmt.Println(unsafe.Pointer(&ns[0]))
s[1] = 20
//输出 [1 20 3 4]
fmt.Println(s)
//输出[20 3]
fmt.Println(ns)
}
结构如下
Slice可以通过copy(dest, src)函数拷贝数据,返回值是成功拷贝的数据个数。
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4}
var d = make([]int, 3, 3)
n := copy(d, s)
//3 [1 2 3]
fmt.Println(n, d)
}
go语言中的函数的参数传递是值传递,就是将数值拷贝一份赋值给实参。如果参数是slice,就是将slice的拷贝一份赋值给实参,slice本质是一个指针指向底层数组,所以可以通过实参改变slice的数值。
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4}
//输出[1 2 3 4]
fmt.Println(s)
foo(s)
//输出[2 3 4 5]
fmt.Println(s)
}
func foo(s []int) {
for i, _ := range s {
s[i] += 1
}
}