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

08 | container包中的那些容器

container/heap包中的堆的适用场景
container/ring包中的循环链表的适用场景
Len方法的算法复杂度
初始化值
表示维度
结构复杂度
延迟初始化机制
链表可以做到开箱即用的原因
链表的PushFrontPushBack方法
链表的InsertBeforeInsertAfter方法
链表的FrontBack方法
避免直接使用自己生成的元素
插入新元素的方法
结论:不会接受,这些方法将不会对链表做出任何改动
MoveToBack方法
MoveToFront方法
MoveAfter方法
MoveBefore方法
Element
List
思考题
问题:RingList的区别在哪儿?
知识扩展
问题解析
问题:可以把自己生成的Element类型值传给链表吗?
container/list代码包
链表所占用的内存空间
链表的原因
正确地使用切片的重要性
切片的不足
切片的好处
切片的特点
Go语言的链表实现
切片与数组的比较
链表
数组和切片
container包中的那些容器
参考文章

该思维导图由 AI 生成,仅供参考

我们在上次讨论了数组和切片,当我们提到数组的时候,往往会想起链表。那么 Go 语言的链表是什么样的呢?
Go 语言的链表实现在标准库的container/list代码包中。这个代码包中有两个公开的程序实体——ListElement,List 实现了一个双向链表(以下简称链表),而 Element 则代表了链表中元素的结构。
那么,我今天的问题是:可以把自己生成的Element类型值传给链表吗?
我们在这里用到了List的四种方法。
MoveBefore方法和MoveAfter方法,它们分别用于把给定的元素移动到另一个元素的前面和后面。
MoveToFront方法和MoveToBack方法,分别用于把给定的元素移动到链表的最前端和最后端。
在这些方法中,“给定的元素”都是*Element类型的,*Element类型是Element类型的指针类型,*Element的值就是元素的指针。
func (l *List) MoveBefore(e, mark *Element)
func (l *List) MoveAfter(e, mark *Element)
func (l *List) MoveToFront(e *Element)
func (l *List) MoveToBack(e *Element)
具体问题是,如果我们自己生成这样的值,然后把它作为“给定的元素”传给链表的方法,那么会发生什么?链表会接受它吗?
这里,给出一个典型回答:不会接受,这些方法将不会对链表做出任何改动。因为我们自己生成的Element值并不在链表中,所以也就谈不上“在链表中移动元素”。更何况链表不允许我们把自己生成的Element值插入其中。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入讨论了Go语言中的`container/list`代码包提供的链表实现,重点介绍了如何使用`List`的方法来操作链表中的元素以及对自定义`Element`值的处理。文章指出了自定义的`Element`值并不直接影响链表的操作,同时解释了链表的特性和`Ring`与`List`的区别。此外,还对`Ring`和`List`在表示方式、初始化、性能和方法方面的差异进行了比较。最后,文章引导读者思考`container/ring`包中的循环链表的适用场景以及`container/heap`包中的堆的适用场景。通过讨论链表的操作方法和特点,本文帮助读者更好地理解`container/list`代码包中链表的使用方法和与其他数据结构的区别。 在文章中,还对切片与数组的比较进行了介绍。切片虽然具有占用内存少、创建便捷等特点,但在删除元素和频繁扩容时会带来一些问题,如元素复制、内存浪费等。同时,文章也提到了链表所占用的内存空间通常比包含相同元素的数组所占内存大得多,但链表的结构相对简单。通过对比,读者可以更好地理解切片、数组和链表的特点和适用场景。 总的来说,本文通过深入讨论链表的操作方法和特点,以及对切片与数组的比较,帮助读者更好地理解Go语言中不同数据结构的使用方法和适用场景。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(40)

  • 最新
  • 精选
  • louis
    郝老师,这里不太理解什么叫“自己生成的Element类型值”?把自己生成的Element类型值传给链表——这个能不能再通俗点描述?

    作者回复: 比如你用 list.New 函数创建了一个 List 类型的双向链表,然后通过它的一些方法往里面塞了一些元素。可以往里面塞的元素的方法有 PushFront、PushBack、InsertAfter、InsertBefore 等。 但是你发现没有,这些方法接受的新元素的类型都是 interface{} 的。也就是说,这个 List 类型的链表只接受 interface{} 类型的新元素值。 而当新元素值进入链表之后,链表会把它们再包装成 list.Element 类型的值。你看,那些往里塞元素值的方法返回的都是被包装后的 *list.Element 类型的元素值。 当你像我这样浏览了 container/list.List 类型的相关 API 之后,就应该可以明白我问这个问题的背景了。 这个 List 类型只会接受 interface{} 类型的新元素值,并且只会吐出 *list.Element 类型的已有元素值。显然,任何移动已有元素值或者删除已有元素值的方法,都只会接受该链表自己吐出来的“Element 值”。因此,对于我们自己生成的“Element 值”,这个链表的任何方法都是不会接受的。 当然了,如果你之前完全没用过 List 类型,可能会觉得这个问题有些突兀。但是当你看完下面的详细解释之后,我相信你就会有所了解了。 我们这个专栏的一个风格就是:“先抛出问题,然后再解释前因后果”。目的就是,逼迫大家在碰到问题之后自己先去了解背景并试着找找答案,然后再回来看我的答案。这样的话,你对这些知识点的记忆会更牢固,不容易忘。 我非常希望这个专栏能成为大家的“枕边书”,而不是听听音频就放在一边的那种。所以才有了这样的结构设计。如果你们能在今后碰到问题时想起这个专栏,到这里翻一翻并能找到答案的线索,那我就太高兴了。

    2019-04-23
    5
    41
  • fliter
    为什么不把list像slice,map一样作为一种不需要import其他包就能使用的数据类型?是因为使用场景较后两者比较少吗

    作者回复: 这需要一个过程,之前list也不是标准库中的一员。况且也没必要把太多的东西多做到语言里,这样反倒不利于后面的扩展。

    2018-08-29
    2
    10
  • Zer0
    不能把自己生成的Element传给List主要是因为Element的list成员是不开放的,我们不能操作,而在List上操作Element的时候是会判断Element的list成员是否是自己。是这样吗?

    作者回复: ✅

    2019-08-06
    6
  • Geek_a8be59
    您好 能否出一个list 链表生成的一个图解,现在我看源码用图去模拟生成 一直搞混掉,特别是在初始化的时候prev和next都指向自身的root 这个很迷糊 比如: c.PushBack("123") c.PushFront("456") c.PushFront("789") 根据个人图解应该是789-》456-》nil,为什么能遍历出来很不清楚。能否有一个从初始化到最后生成的样例看一下 万分感谢

    作者回复: 按照你这三行代码,应该是 789 -> 456 -> 123 啊,你那个“789-》456-》ni”是怎么出来的? 这个链表里所谓的 root 就是用来表示链表两端的尽头的。所以,这个链表的末端实际上并不是 nil ,而是 root。只是在 Element 的 Next 方法中,如果发现它的 next 字段的值等于 root,就会返回 nil 而已。 明白了吗?

    2019-06-26
    2
    6
  • 李斌
    用 vscode 就蛮好的,我之前是八年 vim 党,写 golang 时硬生生地被掰成 vscode

    作者回复: 嗯,也算是与时俱进吧,未来有脑机接口了,也就用不着这些了。

    2018-10-30
    3
    5
  • 雷朝建
    老师, 我看了一下list.go的源码,发现一个疑问是:延迟初始化的含义就是调用lazyInit,它的一个判断条件是:l.root.next==nil; 但是我们在使用list时候,不是先调用New函数吗?那么不应该会出现l.root.next为nil的情况的。 什么时候回出现l.root.next==nil, 从而导致源码中每次的PushFront等操作调用lazyInit呢?

    作者回复: List 是一个 struct 啊,有了 lazyInit 你就可以直接 var myList list.List 了啊。这不是就做到开箱即用了吗?

    2021-04-02
    3
  • 杨震
    以后再有课的话 希望老师多加点图 虽然费点事 但应该更多为学员着想吧。文字阐述一点也不直观。

    作者回复: OK。

    2020-08-25
    3
  • jackstraw
    我尝试打印了 “var l = list.New()” 与 “var l list.List”两种方式的l类型,发现是不一样的,但是下面的操作却都是可以的 func main() { //l := list.New() var l list.List e4 := l.PushBack(4) e1 := l.PushFront(1) l.InsertBefore(3, e4) l.InsertAfter(2, e1) //travel(l) travel(&l) }

    作者回复: 当然不一样,list.New 返回的是指针值。另外你可以再看看讲结构体和方法那篇文章。

    2019-07-09
    3
  • Sky
    这一讲没有实例代码

    作者回复: 建议大家在阅读这一篇文章时对照着 container 包的文档看。对这几个类型的 API 有一定了解之后,使用就是水到渠成的事情了。 自己先试一试,如果有具体的问题,可以来这里问。

    2019-06-12
    2
  • 白有才
    这课程就像小说里的武功秘籍, 看了你不一定会练, 所以练成绝世武功的人就少之又少

    作者回复: 人生的终极意义不就是练成绝世武功嘛!;-)

    2021-08-12
收起评论
显示
设置
留言
40
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部