07 | 数组和切片
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
本文深入介绍了Go语言中的数组和切片类型,重点强调了它们的特点和使用方法。文章首先解释了数组和切片的区别,指出数组长度固定而切片长度可变的特性。接着详细讨论了切片的长度和容量概念,并通过示例清晰地展示了如何正确估算切片的长度和容量。此外,文章还涉及了传值和传引用问题,以及数组和切片的索引表达式和切片表达式的应用。通过生动的比喻,读者能够更好地理解切片与底层数组的关系,以及切片的长度、容量和底层数组之间的联系。 在扩容策略方面,文章详细介绍了切片容量的增长规则,包括对新容量的估算和底层数组的替换情况。此外,还提出了两个思考题,引发读者对切片的深入思考和实践。 总的来说,本文以简洁清晰的语言,深入浅出地介绍了Go语言中数组和切片类型的特点和使用方法,对读者快速了解和掌握这一重要概念具有很高的参考价值。读者可以通过本文全面了解切片的特性、底层数组的替换规则以及对切片的深入思考,从而更好地应用和理解Go语言中的数组和切片类型。
《Go 语言核心 36 讲》,新⼈⾸单¥59
全部留言(97)
- 最新
- 精选
- Nuzar置顶老师的行文用字非常好,不用改!2019-02-22444
- 小小笑儿切片缩容之后还是会引用底层的原数组,这有时候会造成大量缩容之后的多余内容没有被垃圾回收。可以使用新建一个数组然后copy的方式。
作者回复: 没错
2018-08-2969 - 许大老师 go中 make和new 有什么区别
作者回复: make 是专门用来创建 slice、map、channel 的值的。它返回的是被创建的值,并且立即可用。 new 是申请一小块内存并标记它是用来存放某个值的。它返回的是指向这块内存的指针,而且这块内存并不会被初始化。或者说,对于一个引用类型的值,那块内存虽然已经有了,但还没法用(因为里面没有针对那个值的数据结构)。 所以,对于引用类型的值,不要用 new,能用 make 就用 make,不能用 make 就用复合字面量来创建。
2019-08-28567 - 传说中的成大大首先总结今天课程内容 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个问题我可以接着问你:弄新切片的话,那旧切片应该怎么处理?
2020-03-03422 - 传说中的成大大回答追问,旧的切片,无论是扩容或者缩容都会有老的切片释放出来,这个时候应该是被回收了!不然肯定会内存泄露的
作者回复: 如果仍然存在与老切片有关的变量,别忘了置 nil。GC 回收老切片有一个必要条件,那就是:已经没有任何代码引用它了。
2020-03-03212 - 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 函数也会返回一个新的切片。 我已经提交给极客时间的编辑了。如果未能及时更新,你就以我上面写的这两句话为准吧。
2020-12-0127 - wjq310老师,请问下demo16.go的示例三的几个cap值是怎么来的?看这后面的值,不像是2的指数倍。更奇怪的是,我在不同的地方运行(比如把代码贴到https://golang.org/go)得到的结果还不一样,不知道为什么,麻烦帮忙解答一下,感谢了
作者回复: 每次发现容量不够都会翻一倍,你可以从头算一下。另外,一旦超过1024每次只会增大1.25倍。
2018-08-286 - mateye老师您好,就像您说的,切片赋值的话会,如果完全赋值,会指向相同的底层数组, s1 :=[]int{1,2,3,4} s2 := s1[0:4] 就像这样,这样的话改变s2会影响s1,如何消除这种影响呢
作者回复: 可以用copy函数,或者自己深拷贝。
2018-08-304 - Spikeslices 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语言里不会传引用,这我也多次强调过。但是,內建数据类型中会有值类型和引用类型之分。
2018-08-2824 - 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 语句中。
2021-03-1833