Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
78704 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

07 | 数组和切片

从本篇文章开始,我们正式进入了模块 2 的学习。在这之前,我们已经聊了很多的 Go 语言和编程方面的基础知识,相信你已经对 Go 语言的开发环境配置、常用源码文件写法,以及程序实体(尤其是变量)及其相关的各种概念和编程技巧(比如类型推断、变量重声明、可重名变量、类型断言、类型转换、别名类型和潜在类型等)都有了一定的理解。
它们都是我认为的 Go 语言编程基础中比较重要的部分,同时也是后续文章的基石。如果你在后面的学习过程中感觉有些吃力,那可能是基础仍未牢固,可以再回去复习一下。
我们这次主要讨论 Go 语言的数组(array)类型和切片(slice)类型。数组和切片有时候会让初学者感到困惑。
它们的共同点是都属于集合类的类型,并且,它们的值也都可以用来存储某一种类型的值(或者说元素)。
不过,它们最重要的不同是:数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。
数组的长度在声明它的时候就必须给定,并且之后不会再改变。可以说,数组的长度是其类型的一部分。比如,[1]string[2]string就是两个不同的数组类型。
而切片的类型字面量中只有元素的类型,而没有长度。切片的长度可以自动地随着其中元素数量的增长而增长,但不会随着元素数量的减少而减小。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(94)

  • 最新
  • 精选
  • Nuzar
    置顶
    老师的行文用字非常好,不用改!
    2
    43
  • 小小笑儿
    切片缩容之后还是会引用底层的原数组,这有时候会造成大量缩容之后的多余内容没有被垃圾回收。可以使用新建一个数组然后copy的方式。

    作者回复: 没错

    66
  • 许大
    老师 go中 make和new 有什么区别

    作者回复: make 是专门用来创建 slice、map、channel 的值的。它返回的是被创建的值,并且立即可用。 new 是申请一小块内存并标记它是用来存放某个值的。它返回的是指向这块内存的指针,而且这块内存并不会被初始化。或者说,对于一个引用类型的值,那块内存虽然已经有了,但还没法用(因为里面没有针对那个值的数据结构)。 所以,对于引用类型的值,不要用 new,能用 make 就用 make,不能用 make 就用复合字面量来创建。

    5
    65
  • 传说中的成大大
    首先总结今天课程内容 1. 数组和切片的区别与联系 1.1数组是有长度的并且长度是类型的组成部分之一 所以[1]string!=[2]string 长度固定不可变 1.2切片实际上是对底层数组的一层封装,通过切片的容量和长度 我们可以访问到底层数组中对应的元素, 1.2.1如果切片是从底层数组下标为0处开始引用 那个切片的第一个元素(下标为0时)引用的是数组下标为0的元素 1.2.2如果切片是从底层数组下标为3处开始引用那么切片的第一个元素(下标为0时)引用的是数组下标为3的元素 2. 数组和切片的共同点 它们都是集合类型 3. 值传递和引用传递 如果实参是值类型 就是值传递 如果实参为引用类型则是引用传递 一般来说引用传递更快更好 go语言中值类型 : 数组,和内置的数据类型 以及结构体 go语言中引用类型: 切片(slice) 字典(map) 通道(channel) 函数(func) 是引用类型 引用类型一般使用make创建和初始化 4. 关于切片长度和容量的计算 切片长度一般是对底层数组的引用范围 比如s1=s2[3:6] [3,6)引用范围为3-5所以长度为6-3=3,但是切片可以向右扩展而不能向左扩展 所以 s1的容量就 = s2的容量-3 3是对数组引用的起始下标 6是对数组引用的结束下标 5. 关于append和切片扩容 一般使用append对切片进行追加元素 分为以下两种情况 1. 追加过后元素长度小于容量 append返回原切片 2. 追加过后元素长度超过了容量 2.1 如果长度小于1024 则扩容机制为 新切片容量 = 原切片容量*2 返回新切片地址 2.2 如果长度大于1024 则扩容机制为 新切片容量 = 原切片容量*1.25 返回 新切片地址 2.3 如果要追加的元素过多 比切片容量的两倍还多 则再进行前面 2.1 2.2的操作 重点 因为切片必定引用一个底层数组 所以数组也不会是原来的数组了 5. 切片的缩容 回答到思考题当中 思考题答案 1. 如果多个切片引用到同一个数组应该注意什么 这个问题 就像并发问题 多个线程同时操作一块内存区域 所以要注意的是 读写顺序 及读写过后的更新问题 避免本来想读老数据 却被另外一个切片给写入数据了 2. 切片缩容问题 其实可以反向思考 扩容问题 当切片的容量小于等于一定比例后 有大量的空间被浪费 所以新弄一个新切片 容量为原切片按比列缩小 并返回新的切片 代码 等有空了再补上

    作者回复: 赞! 就第2个问题我可以接着问你:弄新切片的话,那旧切片应该怎么处理?

    4
    20
  • 传说中的成大大
    回答追问,旧的切片,无论是扩容或者缩容都会有老的切片释放出来,这个时候应该是被回收了!不然肯定会内存泄露的

    作者回复: 如果仍然存在与老切片有关的变量,别忘了置 nil。GC 回收老切片有一个必要条件,那就是:已经没有任何代码引用它了。

    2
    11
  • Wei Yongchao
    我的这段代码: s := make([]int, 0) fmt.Printf("len(s) = %d, cap(s)=%d, addr=%p\n", len(s), cap(s), s) for i := 1; i <= 10; i++{ s = append(s, 1) fmt.Printf("i:%d, len(s) = %d, cap(s)=%d, addr=%p\n", i, len(s), cap(s), s) } 输出如下: len(s) = 0, cap(s)=0, addr=0x6d0e70 i:1, len(s) = 1, cap(s)=1, addr=0xc00000a0d0 i:2, len(s) = 2, cap(s)=2, addr=0xc00000a0e0 i:3, len(s) = 3, cap(s)=4, addr=0xc0000103a0 i:4, len(s) = 4, cap(s)=4, addr=0xc0000103a0 i:5, len(s) = 5, cap(s)=8, addr=0xc00000e2c0 i:6, len(s) = 6, cap(s)=8, addr=0xc00000e2c0 i:7, len(s) = 7, cap(s)=8, addr=0xc00000e2c0 i:8, len(s) = 8, cap(s)=8, addr=0xc00000e2c0 i:9, len(s) = 9, cap(s)=16, addr=0xc000078000 i:10, len(s) = 10, cap(s)=16, addr=0xc000078000 他在i=3, 4和i=5, 6, 7, 8的时候没有扩容。但,看样子返回的还是以前的切片?

    作者回复: 可能新版本里又有优化了吧。 我看了下最新的源码,确实是“无须扩容时会返回原切片”。 因此,文中需要改动两处: 1. 请记住,在无需扩容时,append 函数返回的是指向原底层数组的原切片,而在需要扩容时,append 函数返回的是指向新底层数组的新切片。 2. 另外,如果新的长度比原有切片的容量还要大,那么底层数组就一定会是新的,而且 append 函数也会返回一个新的切片。 我已经提交给极客时间的编辑了。如果未能及时更新,你就以我上面写的这两句话为准吧。

    2
    7
  • wjq310
    老师,请问下demo16.go的示例三的几个cap值是怎么来的?看这后面的值,不像是2的指数倍。更奇怪的是,我在不同的地方运行(比如把代码贴到https://golang.org/go)得到的结果还不一样,不知道为什么,麻烦帮忙解答一下,感谢了

    作者回复: 每次发现容量不够都会翻一倍,你可以从头算一下。另外,一旦超过1024每次只会增大1.25倍。

    6
  • mateye
    老师您好,就像您说的,切片赋值的话会,如果完全赋值,会指向相同的底层数组, s1 :=[]int{1,2,3,4} s2 := s1[0:4] 就像这样,这样的话改变s2会影响s1,如何消除这种影响呢

    作者回复: 可以用copy函数,或者自己深拷贝。

    4
  • Spike
    slices are not passed by reference, nothing is passed by reference in go. Everything is a copy, every assignment, every parameter to a function, there are no exceptions to this rule. 这是Dave Cheney的原话 slice不是指针也不是引用 希望作者参考下https://dave.cheney.net/2018/07/12/slices-from-the-ground-up 这篇博文

    作者回复: Dave Cheney 又不是Go语言团队中的人。我原创文章中的所有内容都只遵从Go语言官方文档和Go语言源码。你可以再好好看看Go语言规范。再说一遍slice是引用类型之一。另外他的这句话你就没理解对。Go语言里不会传引用,这我也多次强调过。但是,內建数据类型中会有值类型和引用类型之分。

    2
    4
  • Bruce Lee
    老师您好 demo16的例子 s8b := append(s8a, make([]int, 23)...) fmt.Printf("s8b: len: %d, cap: %d\n", len(s8b), cap(s8b)) s8c := append(s8b, make([]int, 45)...) fmt.Printf("s8c: len: %d, cap: %d\n", len(s8c), cap(s8c)) 根据分析应该输出 s8b: len: 44, cap: 44 s8c: len: 89, cap: 89 实际输出 s8b: len: 44, cap: 44 s8c: len: 89, cap: 96 没有明白 希望老师能解答 ```

    作者回复: 其实计算之后新的cap确实是89,但之后编译器又对此进行了优化,变为了96。这是一种关于内存对齐的优化。 详见源码:src/runtime/slice.go 中的函数 growslice,具体的逻辑在这个函数后半部分的 switch 语句中。

    2
    3
收起评论
显示
设置
留言
94
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部