Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24139 人已学习
课程目录
已完结 54 讲
0/4登录后,你可以任选4讲全文学习。
开篇词+学习路线 (3讲)
开篇词 | 跟着学,你也能成为Go语言高手
免费
预习篇 | 写给0基础入门的Go语言学习者
50 | 学习专栏的正确姿势
模块一:Go语言基础知识 (6讲)
01 | 工作区和GOPATH
02 | 命令源码文件
03 | 库源码文件
04 | 程序实体的那些事儿(上)
05 | 程序实体的那些事儿(中)
06 | 程序实体的那些事儿 (下)
模块二:Go语言进阶技术 (16讲)
07 | 数组和切片
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)
23 | 测试的基本规则和流程 (上)
24 | 测试的基本规则和流程(下)
25 | 更多的测试手法
26 | sync.Mutex与sync.RWMutex
27 | 条件变量sync.Cond (上)
28 | 条件变量sync.Cond (下)
29 | 原子操作(上)
30 | 原子操作(下)
31 | sync.WaitGroup和sync.Once
32 | context.Context类型
33 | 临时对象池sync.Pool
34 | 并发安全字典sync.Map (上)
35 | 并发安全字典sync.Map (下)
36 | unicode与字符编码
37 | strings包与字符串操作
38 | bytes包与字节串操作(上)
39 | bytes包与字节串操作(下)
40 | io包中的接口和工具 (上)
41 | io包中的接口和工具 (下)
42 | bufio包中的数据类型 (上)
43 | bufio包中的数据类型(下)
44 | 使用os包中的API (上)
45 | 使用os包中的API (下)
46 | 访问网络服务
47 | 基于HTTP协议的网络服务
48 | 程序性能分析基础(上)
49 | 程序性能分析基础(下)
尾声与思考题答案 (2讲)
尾声 | 愿你披荆斩棘,所向无敌
新年彩蛋 | 完整版思考题答案
Go语言核心36讲
登录|注册

07 | 数组和切片

郝林 2018-08-27
从本篇文章开始,我们正式进入了模块 2 的学习。在这之前,我们已经聊了很多的 Go 语言和编程方面的基础知识,相信你已经对 Go 语言的开发环境配置、常用源码文件写法,以及程序实体(尤其是变量)及其相关的各种概念和编程技巧(比如类型推断、变量重声明、可重名变量、类型断言、类型转换、别名类型和潜在类型等)都有了一定的理解。
它们都是我认为的 Go 语言编程基础中比较重要的部分,同时也是后续文章的基石。如果你在后面的学习过程中感觉有些吃力,那可能是基础仍未牢固,可以再回去复习一下。
我们这次主要讨论 Go 语言的数组(array)类型和切片(slice)类型。数组和切片有时候会让初学者感到困惑。
它们的共同点是都属于集合类的类型,并且,它们的值也都可以用来存储某一种类型的值(或者说元素)。
不过,它们最重要的不同是:数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。
数组的长度在声明它的时候就必须给定,并且之后不会再改变。可以说,数组的长度是其类型的一部分。比如,[1]string[2]string就是两个不同的数组类型。
而切片的类型字面量中只有元素的类型,而没有长度。切片的长度可以自动地随着其中元素数量的增长而增长,但不会随着元素数量的减少而减小。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(58)

  • Nuzar 置顶
    老师的行文用字非常好,不用改!
    2019-02-22
    1
    11
  • 清风徐来
    语言描述有点啰嗦太学术化,和我当时看go并发编程第二版开头几章同样的感觉,希望能更加精简一些,直接突出重点要好很多。
    2018-08-29
    149
  • melon
    初始时两个切片引用同一个底层数组,在后续操作中对某个切片的操作超出底层数组的容量时,这两个切片引用的就不是同一个数组了,比如下面这个例子:
    s1 := []int {1,2,3,4,5}
    s2 := s1[0:5]

    s2 = append(s2, 6)
    s1[3] = 30

    此时s1[3]的值为30, s2[3]的值仍然为4,因为s2的底层数组已是扩容后的新数组了。
    2018-08-27
    63
  • 小小笑儿
    切片缩容之后还是会引用底层的原数组,这有时候会造成大量缩容之后的多余内容没有被垃圾回收。可以使用新建一个数组然后copy的方式。

    作者回复: 没错

    2018-08-29
    17
  • sky
    老师您好!我对源码demo16中示例1、3实际运行结果与预期结果表示ok,但唯独示例2的运行结果觉得没有什么规则可供参考,为何不是下面我预期的结果呢,对于实际的运行结果表示不理解,还烦请老师有空帮忙解答下,感谢!

    代码如下:
    // 示例2
    s7 := make([]int, 1024)
    fmt.Printf("The capacity of s7: %d\n", cap(s7))
    s7e1 := append(s7, make([]int, 200)...)
    fmt.Printf("s7e1: len: %d, cap: %d\n", len(s7e1), cap(s7e1))
    s7e2 := append(s7, make([]int, 400)...)
    fmt.Printf("s7e2: len: %d, cap: %d\n", len(s7e2), cap(s7e2))
    s7e3 := append(s7, make([]int, 600)...)
    fmt.Printf("s7e3: len: %d, cap: %d\n", len(s7e3), cap(s7e3))
    fmt.Println()
    实际运行结果:
    The capacity of s7: 1024
    s7e1: len: 1224, cap: 1280
    s7e2: len: 1424, cap: 1696
    s7e3: len: 1624, cap: 2048

    预期运行结果:
    The capacity of s7: 1024
    s7e1: len: 1224, cap: 1280
    s7e2: len: 1424, cap: 1600
    s7e3: len: 1624, cap: 2000
    2018-09-15
    5
    16
  • Laughing
    1.当两个长度不一的切片使用同一个底层数组,并且两切片的长度均小于数组的容量时,对其中长度较小的一个切片进行append操作,但不超过底层数组容量,这时会影响长度较长切片中原来比较小切片多看到的值,因为底层数组被修改了。
    2. 可以截取切片的部分数据,然后创建新数组来缩容
    2018-08-27
    11
  • 有铭
    谢谢老师,今天这篇文才让我意识到以前对切片的认知是不全面的。但也带来一个新问题,大部分语言里,类似切片的数据结构的实质就是可变数组,他们都没有窗口这个设计,golang是为啥设计了窗口这个功能呢?这个功能在实际开发中能如何应用呢?我想golang这种极简设计思想的语言,绝不会搞多余设计,必然是有某种场景,不用切片的窗口就搞不定。但是我想不出是什么
    2018-08-27
    7
  • 涛哥爱学习
    老师可以多些图表在文章里,方便阅读
    2018-08-30
    4
  • 余泽锋
    1.底层数组的变动会影响多个切片
    2.每一次缩容都需要生成新的切片
    2018-08-27
    4
  • mrly
    老师,我对demo16的运行结果有疑惑,按1024*1.25*1.25*1.25来说,结果应该是这样:
    实际运行结果:
    The capacity of s7: 1024
    s7e1: len: 1224, cap: 1280
    s7e2: len: 1424, cap: 1696
    s7e3: len: 1624, cap: 2048

    预期运行结果:
    The capacity of s7: 1024
    s7e1: len: 1224, cap: 1280=1024*1.25
    s7e2: len: 1424, cap: 1600=1024*1.25*1.25
    s7e3: len: 1624, cap: 2000=1024*1.25*1.25*1.25

    为什么结果不一样呢?
    2018-09-27
    3
  • mateye

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

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

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

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

    2018-08-28
    3
  • 总结一下:
    如果不扩容,新切片和所有基于该底层数组的切片,都对同一个数组进行操作,会相互影响。
    如果扩容,新切片的底层数组会新生成一个,因切片对该数组的操作不会影响原来的数组(原来数组没有引用可能已经被回收了)
    2019-07-21
    2
  • 陈悬高
    虽然 slice 间接引用了底层数组的元素,但是其指针、长度和容量却是它自己的属性。要更新一个 slice 的指针、长度或容量必须使用显式的赋值。从这个角度看,slice 并不是“纯粹”的引用类型,而是像下面这种聚合类型:

    ```
    type IntSlice struct {
    ptr *int
    len, cap int
    }
    ```

    所以,不仅是在调用 `append` 函数时需要更新 slice 变量。另外,对于任何函数,只要有可能改变 slice 的长度或者容量,或者使得 slice 指向不同的底层数组,都需要更新 slice 变量。
    2018-10-14
    2
  • Empty
    王老师,能解释一下demo16里面的第三个示例么
    2018-09-26
    2
  • 徐宁
    能不能少用点前者后者这类语言,很容易困惑又回头去看
    2018-09-06
    2
  • 宇智波悟天
    关于老 slice 容量大于等于 1024 时,没有严格按照1.25倍增长的问题,和大家一样有些困惑,上网查了下。给大家一个参考:
    向 slice 追加元素的时候,若容量不够,会调用 growslice 函数
    func growslice(et *_type, old slice, cap int) slice {
        // ……
        newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
            newcap = cap
        } else {
            if old.len < 1024 {
                newcap = doublecap
            } else {
                for newcap < cap {
                    newcap += newcap / 4
                }
            }
        }
        // ……
        capmem = roundupsize(uintptr(newcap) * ptrSize)
        newcap = int(capmem / ptrSize)

    重点看最后两行代码。
    对 newcap 作了一个内存对齐,这个和内存分配策略相关。进行内存对齐之后,新 slice 的容量是要 大于等于 老 slice 容量的 2倍或者1.25倍的。
    2019-10-14
    1
  • Michael
    第三次看了,老师加了好多图,感激不尽
    2019-05-07
    1
  • 轻轻的飞 ོ
    今天看了 SliceHeader 才终于理解老师说的知识。append操作每次其实都返回了一个新的 SliceHeader,只不过里面的Data数组可能是原来,也可能是新的(由是否扩容决定),这是新旧slice之间可能的惟一关联。所以append操作后的新slice一定要保存好,因为append操作对原来的slice没有任何影响。
    2018-12-24
    1
  • Spike
    我觉得叫指针结构的包装,比叫引用类型更严谨。我查阅了官方文档,没有说slice是引用类型。https://github.com/golang/go/commit/b34f0551387fcf043d65cd7d96a0214956578f94
    在go的注释里去掉了slice是引用类型的语句
    2018-09-23
    1
收起评论
58
返回
顶部