《Go程序设计语言》学习笔记之slice

一. 环境

  Centos8.5, go1.17.5 linux/amd64

二. 概念

1) slice 表示一个拥有相同类型元素的可变长度的序列。slice 通常写成 []T,其中元素的类型是T,它看上去像没有长度的数组类型。

slice 有三个属性:指针、长度、容量。

指针,指向数组的第一个可以从 slice 中访问的元素,这个元素并不一定是数组的第一个元素。

长度,slice 中元素的个数。

容量,从 slice 的起始元素到底层数组最后一个元素间元素的数量。

底层数组与 slice 是一对多的关系,一个底层数组可以对应多个 slice ,这些 slice 可以引用数组的任何位置,彼此之间的元素还可以重叠。

slice 操作符 s[i:j] (其中 0 <= i <= j <= cap(s)) 创建了一个新的 slice ,这个新的 slice 引用了序列 s 中从 i 到 j-1 索引位置的所有元素,这里的 s 既可以是数组或者指向数组的指针,也可以是 slice 。新 slice 的元素个数是 j-i 个。

s[i:j]

2) 零值

slice 类型的零值是 nil。申明一个 slice 类型变量 s ,通过与零值比较,可知它也是个零值。

 8 func main() {
9 var s []int
10 fmt.Println(nil == s)
11 fmt.Printf("s type: %T, len(s): %d, cap(s): %d\n", s, len(s), cap(s))
12 }

运行结果如下

三.申明/初始化

var s []int

1) slice s 引用数组。第9行代码申明了一个 slice s,它与数组的声明很像,但是 [] 中没有指定长度。结果是创建了一个指向数组的 slice。

  8 func main() {
9 s := []int{0, 1, 2, 3, 4, 5}
10 fmt.Printf("type: %T\n", s)
11 fmt.Printf("len: %d\n", len(s))
12 fmt.Printf("cap: %d\n", cap(s))
13 }

运行结果如下

2) slice 类型变量 s1 和 s 引用了同一个底层数组。

  8 func main() {
9 s := []int{0, 1, 2, 3, 4, 5}
10 s1 := s[:]
11 fmt.Println(s1)
12 }

运行结果如下

3) 内置函数 make 可以创建一个具有指定元素类型、长度和容量的 slice 。其中容量参数可以省略,此时,slice 的容量和长度相等。make 其实是创建了一个无名数组并返回了它的一个 slice,这个数组仅可能通过这个 slice 来访问。

示例代码如下

通过打印结果可知,slice 变量 s1 的容量没有指定,默认和其长度一致,两者均为5。slice 变量 s2 仅引用了数组前 5 个元素,但是它的容量是数组的长度,slice 的长度还有增长的空间。slice 变量 s3 与 s2 功能相同。

  8 func main() {
9 s1 := make([]int, 5)
10 s2 := make([]int, 5, 10)
11 s3 := make([]int, 10)[:5]
12
13 fmt.Printf("type: %T\n", s1)
14 fmt.Println("len: ", len(s1))
15 fmt.Println("cap: ", cap(s1))
16 fmt.Println("----------")
17 fmt.Printf("type: %T\n", s2)
18 fmt.Println("len: ", len(s2))
19 fmt.Println("cap: ", cap(s2))
20 fmt.Println("----------")
21 fmt.Printf("type: %T\n", s3)
22 fmt.Println("len: ", len(s3))
23 fmt.Println("cap: ", cap(s3))
24 }

运行结果如下

四. 访问

可通过顺序来访问元素,也可以通过索引来访问元素

  8 func main() {
9 s := []int{0, 1, 2, 3, 4, 5}
10 s1 := s[:len(s)-1]
11
12 for _, e := range s1 {
13 fmt.Printf("%d\t", e)
14 }
15
16 fmt.Println()
17 fmt.Println("----------")
18
19 for i := 0; i < len(s1); i++ {
20 fmt.Printf("%d\t", s1[i])
21 }
22 }

运行结果如下

五. 赋值

支持赋值

  8 func main() {
9 s := []int{0, 1, 2, 3, 4, 5}
10 s1 := s[:len(s)-1]
11 fmt.Printf("s type: %T, len(s): %d, cap(s): %d\n", s1, len(s1), cap(s1))
12
13 s2 := s1
14 fmt.Printf("s type: %T, len(s): %d, cap(s): %d\n", s2, len(s2), cap(s2))
15 fmt.Println(s2)
16 }

运行结果如下

六. 比较

1) slice 无法直接做比较,不能直接使用 == 来测试两个 slice 是否拥有相同的元素。标准库中有 bytes.Equal() 来比较两个字节 slice([]byte)。但对于其它类型的 slice ,需要自定义函数以实现比较。

  8 func main() {
9 s1 := []int{1, 2, 3}
10 s2 := []int{1, 2, 4}
11 fmt.Println(s1 == s2)
12 }

  编译报错,提示 slice 只能与 零值 nil 比较

invalid operation: s1 == s2 (slice can only be compared to nil)

原因有两个,一是 slice 的元素是非直接的,有可能 slice 可以包含它自身;二是底层数组元素有可能会改变,如果底层数组元素改变,同一个 slice 在不同的时间会拥有不同的元素。还需要在实践中去体会。

2) slice 唯一可直接比较的操作是和nil比较

示例代码如下

  8 func main() {
9 s1 := []int{1, 2, 3}
10 s2 := []int{1, 2, 4}
11 var s3 []int
12
13 fmt.Println(nil == s1)
14 fmt.Println(nil == s2)
15 fmt.Println(nil == s3)
16 }

运行结果如下

七. 判空

检查一个 slice 是否为空,请使用 len(s) == 0,而不是 s == nil。在 s != nil 的情况下,slice 也有可能为空。

第9行声明了 slice 类型变量 s1,它是零值,可以通过第21行的打印结果可以知道。此时,它的长度为0。第15行声明了 slice 类型变量 s2,它是一个空的 slice,并非零值  nil,通过第22行的打印结果可知。此时,它的长度也为0。

所以在检查 slice 是否为空时,使用 0 == len(s) 更为合理。示例代码如下

  8 func main() {
9 var s1 []int
10 fmt.Println(s1)
11 fmt.Println("len: ", len(s1))
12 fmt.Println("cap: ", cap(s1))
13
14 fmt.Println("----------")
15 s2 := []int{}
16 fmt.Println(s2)
17 fmt.Println("len: ", len(s2))
18 fmt.Println("cap: ", cap(s2))
19
20 fmt.Println("----------")
21 fmt.Println(s1 == nil)
22 fmt.Println(s2 == nil)
23 }

运行结果如下

八. 字节 slice 与字符串

对字节 slice([]byte) 做 slice 操作,求字符串子串操作,都写作 x[m:n],都返回原始字节的一个子序列,底层引用方式也相同。不同点在于,如果 x 是一字节 slice ,那么 x[m:n] 返回的结果是字节 slice;如果 x 是字符串,那么 x[m:n] 返回的是一个字符串。

九. 修改

引用书中去除空字符串的例子

  8 func nonempty(strings []string) []string {
9 i := 0
10 for _, e := range strings {
11 if "" != e {
12 strings[i] = e
13 i++
14 }
15 }
16
17 return strings[:i]
18 }
19
20 func main() {
21 data := []string{"one", "", "three"}
22 fmt.Printf("%q\n", nonempty(data))
23 fmt.Printf("%q\n", data)
24 }

运行结果如下

十. 使用

1) 直接使用

存储元素

2) 作函数参数

slice 包含了指向数组元素的指针,所以将一个 slice 传递给函数的时候,可以在函数内部修改底层数组的元素。即,创建一个数组的 slice 等于为数组创建了一个别名。

第10行代码,声明了一个 slice t,第20行代码,定义了一个逆序的函数,函数接收的参数是 slice 类型。slice t 中包含了指针属性,所以调用函数,可以直接对 slice t 进行修改。

第15行代码,声明了一个 整型数组 a,调用 reverse() 函数时,需要传入的是 slice 类型,实参写成了 a[:],表示对整个数组 a 的引用。 若实参直接写在 a,即 reverse(a),则会报错 "cannot use a (type [5]int) as type []int in argument ",实参类型与形参类型不一致。

  8 func main() {
9 // slice t
10 t := []int{1, 2, 3, 4, 5}
11 reverse(t)
12 fmt.Println(t)
13
14 // array a
15 a := [...]int{1, 2, 3, 4, 5}
16 reverse(a[:])
17 fmt.Println(a)
18 }
19
20 func reverse(s []int) {
21 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
22 s[i], s[j] = s[j], s[i]
23 }
24 }

运行结果如下

十一. 增长

内置的append函数的增长策略,还没去翻源码,先不说了。

只要有可能改变 slice 的指针、长度或容量,都需要更新 slice 变量。

runes = append(runes, r)

引用书中的例子。示例代码中,每一次添加元素,均更新了 slice 变量 x。

  8 func main() {
9 var x []int
10 x = append(x, 1)
11 x = append(x, 2, 3)
12 x = append(x, 4, 5, 6)
13 x = append(x, x...)
14 fmt.Println(x)
15 }

运行结果如下

十二. 注意

1) 越界

如果 slice 的引用超过了被引用对象的容量,即 cap(s),那么会导致程序宕机。如果 slice 的引用超出了被引用对象的长度,即 len(s),但没有超过被引用对象的容量,那么最终 slice 会比原 slice 长。

声明了两个 slice 变量 Q2 和 summer。slice Q2 引用了数组 months 中 4: "April", 5: "May", 6: "June" 三个元素,长度为3,容量为9。 slice summer 引用了数组 months 中 6: "June", 7: "July", 8: "August" 三个元素,长度为3,容量为7。

第28行代码,声明了一个 slice 变量 endlessSummer ,它引用了 slice 变量 summer,起始位置省略了,endlessSummer 的的起始位置默认即是 summer 的起始位置( 6: "June"),长度为 (5-0),容量为7,endlessSummer 它指向的最后一个元素是  "October"。

第35行代码,打印 从slice 变量 Q2 起始位置开始的 20 个元素,但是编译报错 , “slice 界限超出了容量范围9”。Q2 的起始位置指向原始数组中的元素 “April”,slice Q2 的容量为9,从 “April” 至结束共有9个元素 。打印20个元素,超出了容量。

  8 func main() {
9 months := [...]string{1: "January", 2: "February", 3: "March",
10 4: "April", 5: "May", 6: "June",
11 7: "July", 8: "August", 9: "September",
12 10: "October", 11: "November", 12: "December"}
13
14 Q2 := months[4:7]
15 fmt.Println(Q2)
16 fmt.Printf("type: %T\n", Q2)
17 fmt.Println(len(Q2))
18 fmt.Println(cap(Q2))
19 fmt.Println("----------")
20
21 summer := months[6:9]
22 fmt.Println(summer)
23 fmt.Printf("type: %T\n", summer)
24 fmt.Println(len(summer))
25 fmt.Println(cap(summer))
26 fmt.Println("----------")
27
28 endlessSummer := summer[:5]
29 fmt.Println(endlessSummer)
30 fmt.Printf("type: %T\n", endlessSummer)
31 fmt.Println(len(endlessSummer))
32 fmt.Println(cap(endlessSummer))
33 fmt.Println("----------")
34
35 fmt.Println(Q2[:20])
36 }

运行结果如

《Go程序设计语言》学习笔记之slice的更多相关文章

  1. C程序设计语言学习笔记

    在Windows下运行C语言程序 Windows下的编程工具使用 VC 6.0,下面讲解如何在VC 6.0下运行上节的"Hello, world"程序. 1) 新建Win32 Co ...

  2. Go语言学习笔记十一: 切片(slice)

    Go语言学习笔记十一: 切片(slice) 切片这个概念我是从python语言中学到的,当时感觉这个东西真的比较好用.不像java语言写起来就比较繁琐.不过我觉得未来java语法也会支持的. 定义切片 ...

  3. 2017-04-21周C语言学习笔记

    C语言学习笔记:... --------------------------------- C语言学习笔记:学习程度的高低取决于.自学能力的高低.有的时候生活就是这样的.聪明的人有时候需要.用笨的方法 ...

  4. 2017-05-4-C语言学习笔记

    C语言学习笔记... ------------------------------------ Hello C语言:什么是程序:程序是指:完成某件事的既定方式和过程.计算机中的程序是指:为了让计算机执 ...

  5. GO语言学习笔记(一)

    GO语言学习笔记 1.数组切片slice:可动态增长的数组 2.错误处理流程关键字:defer panic recover 3.变量的初始化:以下效果一样 `var a int = 10` `var ...

  6. Go语言学习笔记六: 循环语句

    Go语言学习笔记六: 循环语句 今天学了一个格式化代码的命令:gofmt -w chapter6.go for循环 for循环有3种形式: for init; condition; increment ...

  7. HTML语言学习笔记(会更新)

    # HTML语言学习笔记(会更新) 一个html文件是由一系列的元素和标签组成的. 标签: 1.<html></html> 表示该文件为超文本标记语言(HTML)编写的.成对出 ...

  8. Haskell语言学习笔记(88)语言扩展(1)

    ExistentialQuantification {-# LANGUAGE ExistentialQuantification #-} 存在类型专用的语言扩展 Haskell语言学习笔记(73)Ex ...

  9. Go语言学习笔记十三: Map集合

    Go语言学习笔记十三: Map集合 Map在每种语言中基本都有,Java中是属于集合类Map,其包括HashMap, TreeMap等.而Python语言直接就属于一种类型,写法上比Java还简单. ...

  10. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

随机推荐

  1. NC224933 漂亮数

    题目链接 题目 题目描述 小红定义一个数满足以下条件为"漂亮数": 该数不是素数. 该数可以分解为2个素数的乘积. 4 是漂亮数,因为 4=2*2 21 是漂亮数,因为 21=3* ...

  2. 【XInput】游戏手柄模拟鼠标动作

    老周一般很少玩游戏,在某宝上买了一堆散件,计划在过年期间自己做个机械臂耍耍.头脑中划过一道紫蓝色的闪电,想起用游戏手柄来控制机械臂.机械臂是由树莓派(大草莓)负责控制,然后客户端通过 Socket U ...

  3. Java并发编程之美

    简介 <Java并发编程之美>分为三部分,第一部分为Java 并发编程基础篇,主要讲解Java 并发编程的基础知识.线程有关的知识和并发编程中的其他相关概念,这些知识在高级篇都会有所使用, ...

  4. centos7安装postgresql9.6

    1.安装yum源 yum install -y https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg- ...

  5. Java压缩和解压缩zip文件

    介绍 Java提供的java.util.zip包只支持zip和gzip.至于更多格式的压缩可以选择apache的Commons Compress. 参考:https://o7planning.org/ ...

  6. Jsp+Servlet实现文件上传下载(二)--文件列表展示

    接着上一篇讲: Jsp+Servlet实现文件上传下载(一)--文件上传 点击打开链接 本章来实现一下上传文件列表展示,同时优化了一下第一章中的代码. 废话少说,上代码 --------------- ...

  7. IoT(Internet of things)物联网入门介绍

    1.什么样的物可以入网? 要有数据传输通路 要有一点的存储功能 要有CPU 要有操作系统 要有专门的应用程序 遵循物联网的通信协议 在网络世界中有可被识别的唯一编号 2.MQTT协议 不是在说物联网吗 ...

  8. NSIS制作安装包笔记(二):NSIS使用NSIS+Qt界面制作安装包流程

    前言   Nsis可以使用duilib也可以使用qt界面,笔者主要qt,本文章梳理nsis+qt制作安装包的基本流程.   下载Nsis-Ui-Plugin插件   Github地址:https:// ...

  9. pyqt5中通过pycharm配置designer(win和mac都适用,修改下designer目录路径即可)

    安装 pip install PyQt5 -i https://pypi.douban.com/simple pip install PyQt5-tools -i https://pypi.douba ...

  10. 《HelloGitHub》第 95 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...