Go语言容器—list

Posted ych9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言容器—list相关的知识,希望对你有一定的参考价值。

文章目录

一、list

  • 概念

    • 列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等
    • 在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作
  • 初始化列表

    • list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的

    • 列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface 类型的值,取出值后,如果要将 interface 转换为其他类型将会发生宕机

      List :=list.New() //初始化方法1
      var List2 list.List //初始化方法2
      
  • 在列表之中插入元素

    • 双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack

    • 这两个方法都会返回一个 *list.Element 结构,如果在以后的使用中*需要删除插入的元素,则只能通过 list.Element 配合 Remove() 方法进行删除,这种方法可以让删除更加效率化,同时也是双链表特性之一

      List :=list.New() //初始化方法1
      
      List.PushBack(12)
      List.PushBack("string")
      
    • 插入元素的方式

      方 法功 能
      InsertAfter(v interface , mark * Element) * Element在 mark 点之后插入元素,mark 点由其他插入函数提供
      InsertBefore(v interface , mark * Element) *Element在 mark 点之前插入元素,mark 点由其他插入函数提供
      PushBackList(other *List)添加 other 列表元素到尾部
      PushFrontList(other *List)添加 other 列表元素到头部
  • 从列表之中删除元素

    • 列表插入函数的返回值会提供一个 *list.Element 结构,这个结构记录着列表元素的值以及与其他节点之间的关系等信息,从列表中删除元素时,需要用到这个结构进行快速删除
  • 遍历列表

    • 遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数

      	List := list.New()
      	List.PushBack(12)
      	element := List.PushBack("string") //尾部添加后保存元素句柄
      
      	List.InsertAfter("after", element) //在string之前插入
      	List.InsertBefore("befor", element)
      
      	for i := List.Front(); i != nil; i = i.Next() 
      		fmt.Print(" ", i.Value) //12 befor string after
      	
      
      	List.Remove(element) //删除
      
      	for i := List.Front(); i != nil; i = i.Next() 
      		fmt.Println(i.Value) //12 befor  after
      	
      

二、空值/零值

  • 概念

    • 在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil
    • nil 是Go语言中一个预定义好的标识符,有过其他编程语言开发经验的开发者也许会把 nil 看作其他语言中的 null(NULL),其实这并不是完全正确的,因为Go语言中的 nil 和其他语言中的 null 有很多不同点。
  • nil 标识符是不能比较的

  • nil 不是关键字或保留字,也就是说我们可以定义一个名称为 nil 的变量,但是并不提倡这么做

  • nil没有默认类型

    fmt.Printf("%T", nil)
    value(nil) //use of untyped nil
    
  • 不同类型 nil 的指针是一样的

    var arr []int
    var num *int
    
    //切片和指针都只声明,那么默认为nil
    fmt.Printf("%p\\n", arr) //0x0
    fmt.Printf("%p", num)   //0x0
    
  • nil 是 map、slice、pointer、channel、func、interface 的零值

    	var m map[int]string
    	var ptr *int
    	var c chan int
    	var sl []int
    	var f func()
    	var i interface
    
    	fmt.Printf("%#v\\n", m)   //map[int]string(nil)
    	fmt.Printf("%#v\\n", ptr) //(*int)(nil)
    	fmt.Printf("%#v\\n", c)   //(chan int)(nil)
    	fmt.Printf("%#v\\n", sl)  //[]int(nil)
    	fmt.Printf("%#v\\n", f)   //(func())(nil)
    	fmt.Printf("%#v\\n", i)   //<nil>
    
    
  • 不同类型的 nil 值占用的内存大小可能是不一样的

    • 一个类型的所有的值的内存布局都是一样的,nil 也不例外,nil 的大小与同类型中的非 nil 类型的大小是一样的。但是不同类型的 nil 值的大小可能不同

    • 具体的大小取决于编译器和架构,下面打印的结果是在 64 位架构和标准编译器下完成的,对应 32 位的架构的,打印的大小将减半。

      var m map[int]string
      	var ptr *int
      	var c chan int
      	var sl []int
      	var f func()
      	var i interface
      
      	fmt.Println(unsafe.Sizeof(m))   //8
      	fmt.Println(unsafe.Sizeof(ptr)) //8
      	fmt.Println(unsafe.Sizeof(c))   //8
      	fmt.Println(unsafe.Sizeof(sl))  //24
      	fmt.Println(unsafe.Sizeof(f))   //8
      	fmt.Println(unsafe.Sizeof(i))   //16
      

三、Go语言make和new关键字的区别和实现原理

3.1 new

  • new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针。同时 new 函数会把分配的内存置为零,也就是类型的零值

    num := new(int)
    fmt.Printf("%#v\\n", num) //(*int)(0xc0000b2008)
    
    *num = 9999
    fmt.Println(*num) //9999
    

3.2 make

  • make 也是用于内存分配的,但是和 new 不同,它只用于 chan、map 以及 slice 的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了
  • new 和 make 主要区别如下
    • make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据
    • new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type
    • new 分配的空间被清零。make 分配空间后,会进行初始化

3.3 实现原理

  • make
    • 在编译期的类型检查阶段,Go语言其实就将代表 make 关键字的 OMAKE 节点根据参数类型的不同转换成了 OMAKESLICE、OMAKEMAP 和 OMAKECHAN 三种不同类型的节点,这些节点最终也会调用不同的运行时函数来初始化数据结构
  • new
    • 内置函数new会在编译器的SSA代码生成阶段经过callnew函数的处理,如果请求创建的类型大小是0,那么就会返回一个表示空指针的zerobase变量,在遇到其它情况时会将关键字转换成newobject
    • 如果当前声明的变量或者参数不需要在当前作用域外生存,那么其实就不会被初始化在堆上,而是会初始化在当前函数的栈中并随着函数调用的结束而被销毁
    • newobject 函数的工作就是获取传入类型的大小并调用 mallocgc 在堆上申请一片大小合适的内存空间并返回指向这片内存空间的指针

以上是关于Go语言容器—list的主要内容,如果未能解决你的问题,请参考以下文章

Go语言的list包(列表)链表

Go语言的list包(列表)链表

Go语言list(列表)

Go 容器之队列的几种实现方式

GO基础之List

通学区块链系列-从go开始容器篇