前言

今天本来想去外地玩耍,结果睡过头错过了动车,只好总结一下slice,希望能与slice之间做一个了断。

文章由浅入深,遵从能用代码说话就不bb的原则。

正文

1.基本操作

1.1 声明

var stringSlice []string
stringSlice := []string{"咖啡色的羊驼"}

var intSlice []int64
intSlice := []int{18}

和数组的区别:就是**[]括号里头不加东西**。

初始化的的一些默认值:

func main() {
	var stringSlice []string
	var intSlice []int64
	fmt.Printf("stringSlice ==> 长度:%v \t地址:%p \t零值是否nil:%v \n",len(stringSlice),stringSlice, stringSlice==nil)
	fmt.Printf("intSlice ==> 长度:%v \t地址:%p \t零值是否nil:%v",len(intSlice),intSlice, intSlice==nil)
}

这里需要注意的是:slice的key必须是数字 && 0开始逐渐增加

1.2 增删改查

// 增
func add(slice []interface{}, value interface{}) []interface{} {
	return append(slice, value)
}

// 删
func remove(slice []interface{}, i int) []interface{} {
	return append(slice[:i], slice[i+1:]...)
}

// 改
func update(slice []interface{}, index int, value interface{}) {
	slice[index] = value
}

// 查
func find(slice []interface{}, index int) interface{} {
	return slice[index]
}

这里需要注意的是:
1.slice的增加需要依赖于append,这里会涉及到扩容机制(后文会说)
2.删除的话,只能是通过切割的方式重拼了,由于slice是引用类型,存的是指针,性能上不会有太多影响

1.3 插入 & 遍历 & 清空

// 插入
func insert(slice *[]interface{}, index int, value interface{}) {
	rear := append([]interface{}{}, (*slice)[index:]...)
	*slice = append(append((*slice)[:index], value), rear...)
}

// 遍历
func list(slice []interface{}) {
	for k, v := range slice {
		fmt.Printf("k:%d - v:%d", k,v)
	}
}

// 清空
func empty(slice *[]interface{}) {
	*slice = append([]interface{}{})
	//    *slice = nil
}

1.3 复制

// 复制
func main() {
	intSlice := []int{1,2,3,4,5,6}
	copySlice1 := make([]int,0,10)

	_ = copy(copySlice1,intSlice)
	fmt.Printf("长度为0的时候:%v\n",copySlice1)

	copySlice2 := make([]int,6,10)

	_ = copy(copySlice2,intSlice)
	fmt.Printf("长度为6的时候:%v",copySlice2)
}

这里需要注意的是:要保证目标切片有足够的大小,注意是大小,而不是容量

2.slice的深入了解

2.1 slice的基础数据结构 & 图

slice的基础数据结构:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

这里的array需要单独说下,这里是指针类型,也说明了slice是引用类型

由浅入深聊聊Golang的slice-LMLPHP
在slice底层,指针指向的是另一个数组。

还是有必要看一下源码中的实现:

// 创建一个slice
func makeslice(et *_type, len, cap int) slice {
    // 检查目标类型的最长长度,slice的len和cap都必须小于这个值
    maxElements := maxSliceCap(et.size)
    if len < 0 || uintptr(len) > maxElements {
        panic(errorString("makeslice: len out of range"))
    }

    // !!! len必须<= cap !!!
    if cap < len || uintptr(cap) > maxElements {
        panic(errorString("makeslice: cap out of range"))
    }

    // 申请内存
    p := mallocgc(et.size*uintptr(cap), et, true)

    // 返回一个slice
    return slice{p, len, cap}
}

2.2 slice切割的底层变化

func main() {
	x:=[]int{1,2,3,4}
	y:=x[1:4]
	fmt.Println(y)
}

// 输出:[2 3 4]

这里需要注意的是:切割遵从左开右闭的原则,就是[1:4],取得是第二个元素到第四个以下的,不包括第四个

来一波图解:
由浅入深聊聊Golang的slice-LMLPHP

2.3 slice的扩容机制 & 实战例子

slice扩容机制还是比较有意思的,上源码:

func growslice(et *_type, old slice, cap int) slice {
    ...

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            for newcap < cap {
                newcap += newcap / 4
            }
        }
    }

    ...

    return slice{p, old.len, newcap}
}

前文很多操作都是基于append的,那么slice在append的时候,如果发生了扩容,那么底层的数组会重建,同时拷贝老的数据到新数组里头。

举个例子:

func main() {
	initSlice := []int{1}
	// 进行扩容到2
	initSlice = append(initSlice, 2)
	// 进行扩容到4
	initSlice = append(initSlice, 3)
	x := append(initSlice, 4)
	y := append(initSlice, 5)
	fmt.Println(initSlice, x, y)
}

会输出:

[1 2 3] [1 2 3 5] [1 2 3 5]

图解说明:
由浅入深聊聊Golang的slice-LMLPHP
由浅入深聊聊Golang的slice-LMLPHP
根据前文介绍的扩容机制,initSlice的扩容轨迹是1-2-4。而slice只是引用类型,所以x和y只是copy了initSlice的指针,他们三个都是指向同一个底层数组,所以最后第四个坑被y给覆盖了。

再举一个正好遇到扩容时候的例子:
我们知道扩容时候是会生成新的底层数组,然后拷贝老的数组值。

func main() {
	initSlice := []int{1}
	// 此时扩容1-2,并且全部装满
	initSlice = append(initSlice, 2)

	// 以下任一append都会引发扩容
	x := append(initSlice, 3)
	y := append(initSlice, 4)
	fmt.Println(initSlice, x, y)
}

输出:

[1 2] [1 2 3] [1 2 4]

图解:
由浅入深聊聊Golang的slice-LMLPHP

由于都遇到了扩容,所以x与y各自另立门户,新建数组,slice指向的底层数组也不同了所以互不干扰了。

3.注意点

3.1 函数传参

func main()  {
	initSlice := []int{1,2,3}
	fmt.Printf("刚开始时候:%v\n",initSlice)
	doSomeThing(initSlice)
	fmt.Printf("一番操作后:%v\n",initSlice)
}

func doSomeThing(s []int)  {
	s[0]=88
	s = append(s, 10)
	fmt.Printf("函数返回前:%v\n",s)
}

输出:

刚开始时候:[1 2 3]
函数返回前:[88 2 3 10]
一番操作后:[88 2 3]

这里面的变化情况抓住一个点:就是发送扩容时候底层数组是新建的!

10-04 20:10