切片 是Go中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。它非常灵活,支持自动扩容。切片的底层一段连续的内存。

切片的内部实现

切片是一个有三个字段的数据结构,分别为地址长度容量,它对底层的数组(内部是通过数组保存数据的)进行了抽象,并提供相关的操作方法。


地址:指向底层数组的指针。
长度:切片可以访问的元素的个数,使用内置函数len(切片名)可以获得。
容量:从切片地址开始到底层数组结尾的长度,使用内置函数cap(切片名)可以获得。

切片的创建和初始化

在Golang中可以通过多种方式创建和初始化切片。可以根据切片所需的容量来决定如何创建切片。

切片的声明

切片的声明格式如下:

 var 切片名 []元素类型例如,声明一个地址为nil的整型切片:var myNum []int   // 例:声明一个地址为nil的整型切片

单纯声明后的切片并没有分配内存空间,因此地址指向nil,可以称之为nil切片。nil切片的长度和容量都为0,数据结构状态如下:

在Golang中,nil切片很常见,可以使用很多标准库和内置函数。在需要描述一个不存在的切片时,nil切片会很好用。比如,函数要求返回一个切片但是发生异常的时候。

通过make()函数创建切片

使用Golang内置的make()函数,动态创建一个切片,格式如下:

    make([]类型, 长度, 容量)a := make([]int, 3, 5)   // 定义一个长度为3,容量为5,元素为int类型的切片fmt.Println(a)           // [0 0 0] 切片可访问的元素数为3fmt.Println(len(a))      // 3fmt.Println(cap(a))      // 5fmt.Printf("%#v \n", a)  // []int{0, 0, 0}

示例代码中a的内部存储空间已经分配了5个,但实际只是用了3个。容量并不会影响当前元素的个数。

当要创建的切片长度=容量时,可以使用以下格式进行创建:

 make([]类型, 长度)         // 创建长度=容量的切片  b := make([]int, 3)      // 定义一个长度为3,容量为3,元素为int类型的切片fmt.Println(a)           // [0 0 0]fmt.Println(len(a))      // 3fmt.Println(cap(a))      // 3fmt.Printf("%#v \n", a)  // []int{0, 0, 0}

创建切片时,长度和容量是可以为0的,此时地址指针不为nil,称为空切片。下图描述了空切片的状态。

通过字面量创建切片

另一种常用的创建切片的方法是使用切片字面量,这种方法和创建数组类似,只是不需要指定[]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定:

 // 创建字符串切片// 其长度和容量都是3个元素myStr := []string{"Jack", "Mark", "Nick"}fmt.Printf("myStr = %#v,长度=%d,容量=%d \n", myStr, len(myStr), cap(myStr))// output: myStr = []string{"Jack", "Mark", "Nick"},长度=3,容量=3// 创建一个整型切片// 其长度和容量都是4个元素myNum := []int{10, 20, 30, 40}fmt.Printf("%#v,长度=%d,容量=%d \n", myNum, len(myNum), cap(myNum))// output: []int{10, 20, 30, 40},长度=4,容量=4

当使用切片字面量创建切片时,还可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面的语法展示了如何使用索引方式创建长度和容量都是100个元素的切片:

 // 创建字符串切片// 使用空字符串初始化第 100 个元素myStr := []string{99: ""}

通过切片/数组创建切片

切片的本质是底层数组切出的一部分,因此可以通过字符串/数组/指向数组或切片的指针构造新切片。
它有两种变体:一种指定low和high两个索引界限值的简单的形式,另一种是除了low和high索引界限值外,还指定容量的完整的形式。
创建新切片的语法如下:

    slice[i:j]   (array[i:j])slice[i:j:k] (array[i:j:k])

i: 表示从 slice/array 的第几个元素开始切
j: 控制切片的长度(j-i),一般不包含索引为j的元素。
k: 控制切片的容量(k-i),如果没有给定 k,则表示切到底层数组的最尾部。
下面是几种常见的简写形式:

 slice[i:]  // 从 i 切到最尾部slice[:j]  // 从最开头(0)切到 j(不包含j)slice[:]   // 从头切到尾,等价于复制整个slice

让我们通过下面的例子来理解通过切片创建新的切片的本质:

 // 通过字面量创建一个整型切片// 其长度和容量都是 5 个元素myNum := []int{10, 20, 30, 40, 50}// 创建一个基于myNum 的新切片// i = 1, j = 3,k未指定,切到最尾部// 长度 = j-i = 2 个元素,容量为 4 个元素newNum := myNum[1:3]fmt.Printf("newNum=%v,长度=%d,容量=%d \n", newNum, len(newNum), cap(newNum))// output: newNum=[20 30],长度=2,容量=4

执行上面的代码后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分:
注意:截取新切片时的原则是 左含右不含。所以 newNum 是从 myNum 的 index=1 处开始截取,截取到 index=3 的前一个元素,也就是不包含 index=3 这个元素。所以,新的 newNum 是由 myNum 中的第2个元素、第3个元素组成,长度为 2,容量为 4。切片 myNum 能够看到底层数组全部 5 个元素的容量,而 newNum 能看到的底层数组的容量只有 4 个元素。newNum 无法访问到底层数组的第一个元素。所以,对 newNum 来说,那个元素就是不存在的。

对切片的操作

为切片添加元素(append())

Go语言的内置函数append()可以为切片动态添加元素。一次可以添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

 var s []int// 添加一个元素s = append(s, 1)       // [1]// 添加多个元素s = append(s, 2, 3, 4) // [1 2 3 4]s2 := []int{5, 6, 7}// 添加另一个切片的所有元素s = append(s, s2...)   // [1 2 3 4 5 6 7]

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化。

切片扩容

相对于数组而言,使用切片的一个好处是:可以按需增加切片的容量。Golang 内置的 append() 函数会处理增加长度时的所有操作细节。要使用 append() 函数,需要一个被操作的切片和一个要追加的值,当 append() 函数返回时,会返回一个包含修改结果的新切片。函数 append() 总是会增加新切片的长度,而容量有可能会改变,也可能不会改变,这取决于被操作的切片的可用容量。

例如:

 func main() {//append()添加元素和切片扩容var numSlice []intfor i := 0; i < 10; i++ {numSlice = append(numSlice, i)fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)}}

输出结果:

 [0] len:1 cap:1 ptr:0xc000014098[0 1] len:2 cap:2 ptr:0xc0000140e0[0 1 2] len:3 cap:4 ptr:0xc0000121e0[0 1 2 3] len:4 cap:4 ptr:0xc0000121e0[0 1 2 3 4] len:5 cap:8 ptr:0xc00000e340[0 1 2 3 4 5] len:6 cap:8 ptr:0xc00000e340[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc00000e340[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc00000e340[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc000018100[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc000018100

从上面的结果可以看出:

  1. append()函数将元素追加到切片的最后,并返回该切片。
  2. 切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

函数 append() 会智能地处理底层数组的容量增长。在切片的容量小于 1024 个元素时,总是会成倍地增加容量。一旦元素个数超过 1024,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量(随着语言的演化,这种增长算法可能会有所改变)。
想要了解切片详细的扩容策略,可以查看$GOROOT/src/runtime/slice.go的源代码。

删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

 func main() {// 从切片中删除元素a := []int{30, 31, 32, 33, 34, 35, 36, 37}// 要删除索引为2的元素(32)a = append(a[:2], a[3:]...)fmt.Println(a) //[30 31 33 34 35 36 37]}

总结一下就是:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

遍历切片

切片的遍历方式和数组是一致的,支持索引遍历for range遍历

func main() {s := []int{1, 3, 5}// 索引遍历for i := 0; i < len(s); i++ {fmt.Println(i, s[i])}// for range遍历for index, value := range s {fmt.Println(index, value)// value += 1  只能修改副本值,无法修改切片元素内容s[index] += 1  //可以修改切片元素内容}
}

需要注意的是,for range创建了每个元素的副本,而不是直接返回对该元素的引用。要想获取每个元素的地址,可以使用切片变量和索引值。

修改切片元素

使用切片变量和索引值修改切片元素的内容。

切片的复制

Golang 内置的 copy() 函数可以将一个切片中的元素拷贝到另一个切片中,其函数声明为:

func copy(dst, src []Type) int

它表示把切片 src 中的元素拷贝到切片 dst 中,返回值为拷贝成功的元素个数。如果 src 比 dst 长,就截断;如果 src 比 dst 短,则只拷贝 src 那部分:

num1 := []int{10, 20, 30}
num2 := make([]int, 5)
count := copy(num2, num1)
fmt.Println(count)
fmt.Println(num2)

运行这段单面,输出的结果为:

3
[10 20 30 0 0]

3 表示拷贝成功的元素个数。

参考资料

(李文周)Go语言基础之切片
Golang 入门 : 切片(slice)

【Go学习笔记】数据类型之切片(slice)相关推荐

  1. Go语言学习笔记-数组、切片、map

    Go语言学习笔记-数组.切片.map 数组:同一数据类型元素的集合.是值类型,长度固定无法修改 声明格式:var 数组名字 [元素数量] 数据类型 var arr [3] int //声明定义了一个长 ...

  2. Redis学习笔记 - 数据类型与API(1)Key

    Redis学习笔记 - 数据类型与API(1)Key Key相关命令 1. 常用命令 命令 含义 时间复杂度 keys 查找所有符合给定模式 pattern 的 key O(N), N 为数据库中 k ...

  3. Numpy学习笔记三——数组切片、bool索引、掩码和花哨索引

    Numpy数组切片.bool索引.掩码和花哨索引 数组切片(slice) 数组切片的公式为 my_array[start: end: step, start: end: step] #示例1: imp ...

  4. Python3学习笔记-数据类型和变量

    有C++基础,一直对"万能"的Python语言感兴趣,目前正在学习廖雪峰老师的Python3教程和其他资料用来入门,这里记录一些没接触过或与C++有差异的知识,方便自己查阅吧~ 字 ...

  5. Python学习笔记--数据类型

    Python数据类型 数据类型 操作符 数值运算函数 字符串类型 字符串操作符 字符串处理函数 字符串处理方法 字符串类型的格式化 time库的使用 时间获取 时间格式化 程序计时 实例(文本进度条) ...

  6. Python学习笔记之列表切片(六)

    1.切片简单描述 什么是切片:在Python中处理列表的部分元素,称之为切片.创建切片,可指定要使用的第一个元素和最后一个元素的索引,示例代码如下: ​#列表切片lists = ['张学友','刘德华 ...

  7. go语言数据类型之切片slice

    初识 Slice(切片)代表变长的序列,序列中每个元素都有相同的类型.一个slice类型一般写作[]T,其中T代表slice中元素的类型:slice的语法和数组很像,只是没有固定长度而已,slice在 ...

  8. JS高级程序设计【红宝书】学习笔记——数据类型

    目录 数据类型 Number类型 1.值的范围 2.NaN 3.数值转换 String类型 1.字符字面量(详见JS高级程序设计P63 2.转换为字符串 Symbol类型 Object类型 objec ...

  9. C语言学习笔记-数据类型

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 C语言数据类型 一.编程规范 1.1代码缩进 1.2变量.常量命名规范 1.3函数的命名规范 1.4注释 二.关键字 2.1 32个关 ...

  10. mysql timdir_MYSQL学习笔记——数据类型

    mysql的数据类型可以分为三大类,分别是数值数据类型.字符串数据类型以及日期时间数据类型. 数值数据类型 数值类型又可以分为整型.浮点类型.Decimal. 整型 mysql的整型可以分为TINYI ...

最新文章

  1. Visual Studio 2017强制更新方法
  2. linux shell 里面执行python 程序_Linux下编写脚本Shell和Python的区别?
  3. 鸟哥的Linux私房菜(基础篇)- 鸟哥的第一本书的主要内容,以 Mandrake 9.0 为例
  4. 容器中构建镜像慢,在dockerfile中换源加速
  5. mysql执行计划中的temp_MYSQL语句调优:GROUP BY ORDER BY语句中出现USING TEMPATORY
  6. 干货!9种高性能可用高并发的技术架构
  7. MVC html 控件扩展【转载】
  8. 网页闯关游戏(riddle webgame)--仿微信聊天的前端页面设计和难点
  9. Linux网络配置与远程连接
  10. android实现应用程序只有在第一次启动时显示引导界面
  11. 路飞学城—Python—爬虫实战密训班 第三章
  12. 淘宝R2去模糊化+聚石塔+奇门
  13. 工程数学(经常用到的工程数学知识进行整理)
  14. pytorch1.10新功能inference_mode
  15. MFC中得到2个SYSTEMTIME时间差的函数
  16. Laravel的env和config傻傻分不清?
  17. Windows中命令行收集
  18. ISM330DHCXTR IMU-惯性测量单元 工业物联网 运动传感器
  19. Android相机开发和遇到的坑
  20. 搭建多节点Fabric网络(Windows系统)

热门文章

  1. 乱码解决(二)——文件转码
  2. 「弟子入則孝,出則弟,謹而信,泛愛眾,而親仁,行有餘力,則以學文。」...
  3. 想要学习Java,没有英语基础可以学吗?
  4. laradock 国内版
  5. 海康威视工业相机SDK二次开发环境配置—Windows10+VS2017
  6. eNSP构建企业私有网络
  7. 数据库中间件Mycat介绍详解
  8. spring 1.0-5.0版本注解发展史(一)
  9. 适用于ios5的应用_适用于设计人员和开发人员的10个很棒的iOS应用
  10. 人生修煉電影篇之-------------------- 《阿丽塔:战斗天使》