slice
数据结构
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
array:指向底层数组的指针
len:是slice的长度,当前数据成员数;是下标操作的上界;x[i],其中必须i<len;len<=cap
cap:是slice的容量,最多可容纳的数据个数;也是分割操作的上界,x[i:j],其中j<=cap
注意,底层数组是可以被多个 slice 同时指向的,因此对一个 slice 的元素进行操作是有可能影响到其他 slice 的。
slice创建
5种方式
序号 | 方式 | 代码示例 |
---|---|---|
1 | 直接声明 | var slice []int |
2 | new | slice:=*new([]int) |
3 | 字面量 | slice:=[]int |
4 | make | slice:=make([]int,5,10) |
5 | 从切片或数组"截取" | slice:=array[1:5] 或 slice:=srcSlice[1:5] |
slice三种状态
nil slice,empty slice,zero slice
创建方式 | nil slice | empty slice | zero slice |
---|---|---|---|
方式1 | var s1 []int | var s2=[]int{} | var s5=make([]int,2) |
方式2 | var s3=*new([]int) | var s4= make([]int,0) | var s6=make([]*int,2) |
*arr | nil | ADDR | [0,0] , [nil,nil] |
len | 0 | 0 | 2 |
cap | 0 | 0 | 2 |
==nil | true | false | false |
func main() {
//nil slice
var s1 []int
var p1 = (*[3]int)(unsafe.Pointer(&s1))
fmt.Printf("%x,%x,%x,%v,%#v\n", *p1, p1[0], *(*[]int)(unsafe.Pointer(&p1[0])), s1 == nil, s1)
//empty slice
var s2 = []int{}
var p2 = *(*[3]int)(unsafe.Pointer(&s2))
fmt.Printf("%x,%x,%x,%v,%#v\n", p2, p2[0], *(*[]int)(unsafe.Pointer(&p2[0])), s2 == nil, s2)
var s3 = make([]int, 0)
var p3 = *(*[3]int)(unsafe.Pointer(&s3))
fmt.Printf("%x,%x,%v,%v,%#v\n", p3, p3[0], *(*[]int)(unsafe.Pointer(&p3[0])), s3 == nil, s3)
//zero slice
var s4 = make([]int, 5)
var p4 = *(*[3]int)(unsafe.Pointer(&s4))
fmt.Printf("%x,%x,%v,%v,%#v\n", p4, p4[0], *(*[]int)(unsafe.Pointer(&p4[0])), s4 == nil, s4)
}
[0 0 0],0,[],true,[]int(nil)
[6ff950 0 0],6ff950,[],false,[]int{}
[6ff950 0 0],6ff950,[],false,[]int{}
[c0000b4090 5 5],c0000b4090,[0 0 0 0 0],false,[]int{0, 0, 0, 0, 0}
nil slice和Empty slice的长度和容量都为0,输出时的结果都是[],且都不存储任何数据,但它们是不同的。nil slice不会指向底层数组,而空slice都会指向同一个共享底层数组,只不过这个底层数组暂时是空数组;
空切片指向的底层共享地址zerobase
//// runtime/malloc.go
// base address for all 0-byte allocations
var zerobase uintptr
// 分配对象内存
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
...
if size == 0 {
return unsafe.Pointer(&zerobase)
}
...
}
//// runtime/slice.go
// 创建切片
func makeslice(et *_type, len, cap int) slice {
...
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
}
结构体中empty slice和nil slice
func main() {
type Something struct {
values []int
}
var s1 = Something{}
var s2 = Something{[]int{}}
fmt.Println(s1.values == nil)
fmt.Println(s2.values == nil)
}
true
false
JSON 序列化中的empty slice和nil slice
func main() {
type Something struct {
Values []int
}
var s1 = Something{}
var s2 = Something{[]int{}}
bs1, _ := json.Marshal(s1)
bs2, _ := json.Marshal(s2)
fmt.Println(string(bs1))
fmt.Println(string(bs2))
}
{"Values":null}
{"Values":[]}
截取
slice和array的区别
- slice 的底层数据是数组,slice 是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。
- 数组是定长的,长度定义好之后,不能再更改。在 Go 中,数组是不常见的,因为其长度是类型的一部分,限制了它的表达能力,比如 [3]int 和 [4]int 就是不同的类型。而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。
append
nil slice
或者 empty slice
都是可以通过调用 append 函数来获得底层数组的扩容。最终都是调用 mallocgc
来向 Go 的内存管理器申请到一块内存,然后再赋给原来的nil slice
或 empty slice
,然后摇身一变,成为“真正”的 slice
了。
作参数
不管传的是 slice 还是 slice 指针,如果改变了 slice 底层数组的数据,会反应到实参 slice 的底层数据
Go 语言的函数参数传递,只有值传递,没有引用传递
func main() {
s := []int{1, 1, 1}
f(s)
fmt.Println(s)
}
func f(s []int) {
// i只是一个副本,不能改变s中元素的值
for _, i := range s {
i++
}
}
[1 1 1]
- 改变底层值
func main() {
s := []int{1, 1, 1}
f(s)
fmt.Println(s)
}
func f(s []int) {
for i := range s {
s[i] += 1
}
}
[2 2 2]
- 改变外层slice本身
func main() {
s := []int{1, 1, 1}
f(s)
fmt.Println(s)
}
func f(s []int) {
s = append(s, 5)
for i := range s {
s[i] += 1
}
fmt.Println(s)
}
[2 2 2 6]
[1 1 1]
//结果是两个不同的slice
func main() {
s := []int{1, 1, 1}
f(&s)
fmt.Println(s)
}
func f(s *[]int) {
*s = append(*s, 5)
for i := range *s {
(*s)[i] += 1
}
fmt.Println(*s)
[2 2 2 6]
[2 2 2 6]