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的区别

  1. slice 的底层数据是数组,slice 是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。
  2. 数组是定长的,长度定义好之后,不能再更改。在 Go 中,数组是不常见的,因为其长度是类型的一部分,限制了它的表达能力,比如 [3]int 和 [4]int 就是不同的类型。而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。

append

nil slice 或者 empty slice 都是可以通过调用 append 函数来获得底层数组的扩容。最终都是调用 mallocgc 来向 Go 的内存管理器申请到一块内存,然后再赋给原来的nil sliceempty 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]

扩容

posted @ 2021-10-12 21:45  wangzhilei  阅读(523)  评论(0)    收藏  举报