go 切片slice学习总结

切片的结构

切片的底层结构:

复制代码
type SliceHeader struct {
	Data uintptr  // 指向底层数组的指针 
	Len  int      //长度
	Cap  int      //空间容量
}

切片的初始化

1 通过数组或者已有的slice创建新的slice

1.1 使用数组创建切片

通过数组的一部分来初始化切片。

复制代码
array := [10]int{0,1, 2, 3, 4, 5,6,7,8,9}  
slice := a[5:7] 

Slice将与原数组共用一部分内存。

1.2 通过slice创建新的切片对象

复制代码
x := []int{2, 3, 5, 7, 11}  
y := x[1:3]

x: 长度len=5 cap=5 data指针指向长度为5的底层数组结构。

y 长度为2 cap为y底层原始数组结构第一个元素位置到的最后一个容量空间位置的长度即5-1=4

data为指针指向底层原始数组结构第1个元素的地址(索引从0开始)

图示如下:

2,使用make函数

make函数是Go中用于分配和初始化内置类型的内置函数,也可以用来创建切片。

复制代码
slice := make([]int, 5, 10) // 创建一个长度为5、容量为10的int类型切片

长度为5,即可以使用下标slice[0] ~ slice[4]来操作里面的元素,capacity为10,表示后续向slice添加新的元素时可以不必重新分配内存,直接使用预留内存即可。

3,切片字面量

直接使用切片字面量创建切片。

复制代码
s := []int{1, 2, 3} // 直接初始化一个切片

4,较特殊的切片:

nil 切片

复制代码
var a []int 

未初始化的切片默认值为nil

空切片

复制代码
var b = []int{} 

也叫零值切片。和 nil 不相等, 一般用来表示一个空的集合。len 和 cap 都为 0。空切片在内部拥有一个非nil的、零长度的底层数组

在判断一个切片是否为空时,一般通过 len 获取切片的长度来判断,一般很少将切片和 nil 值做直接的比较

数组与切片

数组截取元素生成切片,共用底层元素存储底层数组结构

复制代码
func main() {
	data := [...]int{0, 1, 2, 3, 4, 5}

	s := data[2:4]
	s[0] += 100
	s[1] += 200

	fmt.Println(s)
	fmt.Println(data)
}

打印结果:

102 203

0 1 102 203 4 5

切片赋值

复制代码
	data := [...]int{0, 1, 2, 3, 4, 5}

	s2 := data[:]
	fmt.Println("s2是:", s2)

	s3 := s2
	fmt.Println("s3是:", s3)
	s3[0] = 1000 + s3[0]
	fmt.Println("s3是:", s3)
	fmt.Println("s2是:", s2)
    fmt.Println("data是:", data)

打印结果:

s3是: [1000 1 2 3 4 5]
s2是: [1000 1 2 3 4 5]
data是: [1000 1 2 3 4 5]

切片参数

函数参数是切片时,是引用传递,函数内对切片的改动影响源切片

复制代码
    func main(){
    sl := []int{1, 2, 3, 4, 5, 6, 7}
	PrintElements(sl)
	fmt.Printf("slice a : %v\n", sl)
  }


func PrintElements(sl []int) {
	fmt.Println("-------------")
	for i, v := range sl {
		fmt.Println(v)
		sl[i] = v + 1
	}
}

打印结果:


1

2

3

4

5

6

7

slice a : [2 3 4 5 6 7 8]

切片容量

切片扩容

扩容实际上是重新分配一块更大的内存,将原Slice数据拷贝进新Slice,然后返回新Slice,扩容后再将数据追加进去。

例如,当向一个capacity为5,且length也为5的Slice再次追加1个元素时,就会发生扩容,如下图所示:

扩容操作只关心容量,会把原Slice数据拷贝到新Slice,追加数据由append在扩容结束后完成。上图可见,扩容后新的Slice长度仍然是5,但容量由5提升到了10,原Slice的数据也都拷贝到了新Slice指向的数组中。

扩容容量的选择遵循以下规则:

  • 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;
  • 如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍;

使用append()向Slice添加一个元素的实现步骤如下:

  • 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
  • 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
  • 将新元素追加进新Slice,Slice.len++,返回新的Slice。

参考:slice-地鼠文档

切片字面量创建的切片

slice := []int{1, 2, 3, 4, 5, 6, 7, 8}

当你使用字面量来创建切片时,切片的初始容量(capacity)通常等于其长度(length)。这意味着,例子slice := []int{1, 2, 3, 4, 5, 6, 7, 8},切片的长度和容量都是8。

复制代码
	slice := []int{1, 2, 3, 4, 5, 6, 7, 8}
	fmt.Print("cap:", cap(slice))
	fmt.Print("len:", len(slice))

cap: 8

len: 8

扩容:

复制代码
	slice := []int{1, 2, 3, 4, 5, 6, 7, 8}
	fmt.Println("cap:", cap(slice))
	fmt.Println("len:", len(slice))
	fmt.Println("----------append后扩容----------")
	slice2 := append(slice, 0) // 切片扩展 1 个空间
	fmt.Println("cap:", cap(slice2))
	fmt.Println("len:", len(slice2))
	fmt.Println(&slice[0] == &slice2[0])

打印结构

cap: 8

len: 8

----------append后扩容----------

cap: 16

len: 9

false

append元素后容量不够使用,扩容为原来的两倍

make创建的切片

make创建的切片,类型申明之后,第一个数值是长度,第二个数值是容量

复制代码
slice2 := make([]int, 3, 7)
	fmt.Println("cap:", cap(slice2))
	fmt.Println("len:", len(slice2))

cap: 7

len: 3

append元素后,容量不够就会扩容为之前的两倍容量。随之是地址的改动。

复制代码
	slice2 := make([]int, 3, 7)
	fmt.Println("cap:", cap(slice2))
	fmt.Println("len:", len(slice2))
	slice3 := append(slice2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 切片扩展 1 个空间
	fmt.Println("----------append后扩容----------")
	fmt.Println("cap:", cap(slice3))
	fmt.Println("len:", len(slice3))
	fmt.Println(&slice2[0] == &slice3[0])

cap: 7

len: 3

----------append后扩容----------

cap: 14

len: 13

false

切片操作

1,append操作

使用append函数,生成新的切片

append函数返回的slice跟原slice是独立的slice,互不影响

复制代码
    var a = []int{1, 2, 3}
	fmt.Printf("slice a : %v\n", a)
	var b = []int{4, 5, 6}
	fmt.Printf("slice b : %v\n", b)
	c := append(a, b...)
	fmt.Printf("slice c  : %v\n", c)
	b = append(b, 7)
	fmt.Printf("slice b  after apend 7: %v\n", b)
	a = append(a, 7)
	fmt.Printf("slice a  after apend 7: %v\n", a)
	fmt.Printf("slice c : %v\n", c)
	c = append(c, 7)
	fmt.Printf("slice c  after apend 7: %v\n", c)

打印结果:

slice a : [1 2 3]

slice b : [4 5 6]

slice c : [1 2 3 4 5 6]

slice b after apend 7: [4 5 6 7]

slice a after apend 7: [1 2 3 7]

slice c : [1 2 3 4 5 6]

slice c after apend 7: [1 2 3 4 5 6 7]

其中b...是使用...展开运算符将slice展开作为单独元素传递给函数使用

以上apend操作是在末尾添加,还可以在头部添加

复制代码
s := []int{2, 3, 4, 5, 6, 7}
	s = append([]int{1}, s...)
	fmt.Println("s是:", s)
	s = append([]int{-1, -2}, s...)
	fmt.Println("s是:", s)

s是: [1 2 3 4 5 6 7]

s是: [-1 -2 1 2 3 4 5 6 7]

2,切片copy

copy(dst,src)将src内容复制给dst切片

复制代码
src := []int{1, 2, 3}
	dst := make([]int, 3)
	copy(dst, src) // 现在dst是[1, 2, 3]的拷贝
	fmt.Println("dst是:", dst)

copy复制后两个切片是独立的

复制代码
src := []int{1, 2, 3}
	dst := make([]int, 3)
	copy(dst, src) // 现在dst是[1, 2, 3]的拷贝
	fmt.Println("dst是:", dst)
	dst = append(dst, 4)
	fmt.Println("dst是:", dst)
	fmt.Println("src是:", src)

dst是: [1 2 3]

dst是: [1 2 3 4]

src是: [1 2 3]

copy与append实现高效 添加元素

复制代码
a = append(a, 0)     // 切片扩展 1 个空间
	copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置
	a[i] = x             // 设置新添加的元素

具体实现:

复制代码
a := []int{1, 2, 3, 4, 5, 6, 7, 8}
	a = append(a, 0)     // 切片扩展 1 个空间
	copy(a[4+1:], a[4:]) // a[i:] 向后移动 1 个位置
	a[4] = 88            // 设置新添加的元素
	fmt.Println("a是:", a)

打印:

a是: [1 2 3 4 88 5 6 7 8]

3,切片删除

从末尾删除

复制代码
slice5 := []int{1, 2, 3, 4, 5, 6, 7, 8}
	new_slice5 := slice5[:len(slice5)-1]
	fmt.Println("new_slice5:", new_slice5)
	new_slice5 = slice5[:len(slice5)-5]
	fmt.Println("new_slice5:", new_slice5)

new_slice5: [1 2 3 4 5 6 7]

new_slice5: [1 2 3]

从头部删除

slice5 = slice5[1:] //删除头部第1个元素

slice5 = slice[5:] //删除头部第N个元素

使用append删除

append删除元素非常灵活,可以删除头部,中间,尾部元素。注意若是希望地址不变,需要将append的结果赋值回去

删除头部元素

复制代码
slice5 = append(slice5[:0], slice5[3:]...) //删除头部元素
	fmt.Println("slice5:", slice5)

slice5: [4 5 6 7 8]

删除中间元素

复制代码
	slice5 = append(slice5[:3], slice5[5:]...) //删除中间两个元素
	fmt.Println("slice5:", slice5)

slice5: [1 2 3 6 7 8]

删除尾部元素

复制代码
slice5 = append(slice5[:0], slice5[0:len(slice5)-3]...) //删除尾部元素
	fmt.Println("slice5:", slice5)

slice5: [1 2 3 4 5]

以上便是对slice的学习总结,若是不当之处,烦请指出。后面有新的学习领悟,会继续添加。

相关推荐
zhougl99625 分钟前
html处理Base文件流
linux·前端·html
花花鱼29 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_32 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之3 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端3 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷6 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript