学习笔记集装箱包中的那些容器

  

参考:https://time.geekbang.org/column/article/14117

  

1,那么去语言的链表是什么样的呢?

  

去语言的链表实现在标准库的容器/列表代码包中。这个代码包中有两个公开的程序实体——和元素列表,列表实现了一个双向链表(以下简称链表),而元素则代表了链表中元素的结构。

  

2,可以把自己生成的元素类型值传给链表吗?

  

这里用到了列表的四种方法。
MoveBefore方法和很方法,它们分别用于把给定的元素移动到另一个元素的前面和后面.MoveToFront方法和MoveToBack方法,分别用于把给定的元素移动到链表的最前端和最后端。在这些方法中,“给定的元素”都是元素类型的,元素类型是元素类型的指针类型,*元素的值就是元素的指针。

  

果我们自己生成这样的值,然后把它作为“给定的元素”传给链表的方法,那么会发生什么?链表会接受它吗?

  
 <代码> func (l *列表)MoveBefore (e,马克*元素)
  func (l *列表)(e,马克*元素)很
  
  func (l *列表)MoveToFront (e *元素)
  func (l *列表)MoveToBack (e *元素) 
  

答案:不会接受,这些方法将不会对链表做出任何改动。因为我们自己生成的元素值并不在链表中,所以也就谈不上“在链表中移动元素”。更何况链表不允许我们把自己生成的元素值插入其中。

  

在列表包含的方法中,用于插入新元素的那些方法都只接受界面{}类型的值。这些方法在内部会使用元素值,包装接收到的新元素。这样做正是为了避免直接使用我们自己生成的元素,主要原因是避免链表的内部关联,遭到外界破坏,这对于链表本身以及我们这些使用者来说都是有益的。

  

3,列表的方法还有下面这几种:

  

正面和背面方法分别用于获取链表中最前端和最后端的元素
方法和InsertAfter方法分别用于在指定的元素之前和之后插入新元素
PushFront和阻力方法则分别用于在链表的最前端和最后端插入新元素。

  

这些方法都会把一个元素值的指针作为结果返回,它们就是链表留给我们的安全“接口”。拿到这些内部元素的指针,我们就可以去调用前面提到的用于移动元素的方法了。

  
 <代码> func (l *列表)前()*元素
  func (l *列表)()*元素
  
  func (l *列表)方法(v接口{},马克*元素)*元素
  func (l *列表)InsertAfter (v接口{},马克*元素)*元素
  
  func (l *列表)PushFront ({}) v接口*元素
  func (l *列表)阻力({})v接口*元素 
  

4,为什么链表可以做到开箱即用?

  

列表和元素都是结构体类型。结构体类型有一个特点,那就是它们的零值都会是拥有特定结构,但是没有任何定制化内容的值,相当于一个空壳。值中的字段也都会被分别赋予各自类型的零值。

  

广义来讲,所谓的零值就是只做了声明,但还未做初始化的变量被给予的缺省值。每个类型的零值都会依据该类型的特性而被设定。比如,经过语句var int[2]声明的变量一个的值,将会是一个包含了两个0的整数数组。又比如,经过语句var年代int[]声明的变量s的值将会是一个int[]类型的,值为nil的切片。

  

那么经过语句var l列表。列表声明的变量l的值将会是什么呢?[1]这个零值将会是一个长度为0的链表。这个链表持有的根元素也将会是一个空的壳,其中只会包含缺省的内容。那这样的链表我们可以直接拿来使用吗?

  

答案是:可以的。这被称为“开箱即用“.Go语言标准库中很多结构体类型的程序实体都做到了开箱即用。这也是在编写可供别人使用的代码包(或者说程序库)时,我们推荐遵循的最佳实践之一。那么,语句var l list.List声明的链表l可以直接使用,这是怎么做到的呢?

  

关键在于它的“延迟初始化”机制。所谓的延迟初始化,你可以理解为把初始化操作延后,仅在实际需要的时候才进行。延迟初始化的优点在于“延后”,它可以分散初始化操作带来的计算量和存储空间消耗。

  

例如,如果我们需要集中声明非常多的大容量切片的话,那么那时的CPU和内存空间的使用量肯定都会一个激增,并且只有设法让其中的切片及其底层数组被回收,内存使用量才会有所降低。

  

如果数组是可以被延迟初始化的,那么计算量和存储空间的压力就可以被分散到实际使用它们的时候。这些数组被实际使用的时间越分散,延迟初始化带来的优势就会越明显。

  

学习笔记集装箱包中的那些容器