go语言坑之list删除所有元素

东方客主 等级 963 0 0

go提供了一个list包 类似python的list,可以存储任意类型的数据,并提供了相应的API,如下:

type Element
    func (e *Element) Next() *Element
    func (e *Element) Prev() *Element
type List
    func New() *List
    func (l *List) Back() *Element
    func (l *List) Front() *Element
    func (l *List) Init() *List
    func (l *List) InsertAfter(v interface{}, mark *Element) *Element
    func (l *List) InsertBefore(v interface{}, mark *Element) *Element
    func (l *List) Len() int
    func (l *List) MoveAfter(e, mark *Element)
    func (l *List) MoveBefore(e, mark *Element)
    func (l *List) MoveToBack(e *Element)
    func (l *List) MoveToFront(e *Element)
    func (l *List) PushBack(v interface{}) *Element
    func (l *List) PushBackList(other *List)
    func (l *List) PushFront(v interface{}) *Element
    func (l *List) PushFrontList(other *List)
    func (l *List) Remove(e *Element) interface{} 

借助list包提供的API,list用起来确实挺方便,但是在使用过程中,如果不注意就会遇到一些难以发现的坑,导致程序结果不是预想的那样。这里要说的坑是通过for循环遍历list,并删除所有元素时会遇到的问题。例如,下面这个示例程序创建了一个list,并依次将0-3存入,然后通过for循环遍历list删除所有元素:

package main

import (
    "container/list"
    "fmt"
)

func main() {

    l := list.New()
    l.PushBack(0)
    l.PushBack(1)
    l.PushBack(2)
    l.PushBack(3)
    fmt.Println("original list:")
    prtList(l)

    fmt.Println("deleted list:")

    for e := l.Front(); e != nil; e = e.Next() {
        l.Remove(e)
    }

    prtList(l)
}

func prtList(l *list.List) {
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Printf("%v ", e.Value)
    }
    fmt.Printf("\n")
} 

运行程序输出如下:

original list:
0 1 2 3 
deleted list:
1 2 3 

从输出可以知道,list中的元素并没有被完全删除,仅删除了第一个元素0,和最初设想不一样,按照go的使用习惯,遍历一个list并删除所有元素写法应该如下:

for e := l.Front(); e != nil; e = e.Next() {
    l.Remove(e)
} 

但是根据上面示例代码的输出,这样删除list所有元素是无效的,那么问题出在哪呢?由for循环的机制可以知道,既然删除了第一个元素,没有删除第二个元素,肯定是第二次循环的条件无效,才导致循环退出,即执行完下面语句后:

l.Remove(e) 

e应该为nil,所以循环退出。在for循环中的l.Remove(e)语句前添加打印语句验证,例如添加如下语句:

fmt.Println("delete a element from list") 

运行程序输出如下:

original list:
0 1 2 3 
deleted list:
delete a element from list
1 2 3 

可以看到,确实只循环了一次,循环就结束了。即当执行完语句l.Remove(e)后,e等于e.Next(),因为e.Next()为nil,导致e为nil,循环退出。为什么e.Next()会是nil呢?通过查看go list源码,如下所示:

// remove removes e from its list, decrements l.len, and returns e.
func (l *List) remove(e *Element) *Element {
    e.prev.next = e.next
    e.next.prev = e.prev
    e.next = nil // avoid memory leaks
    e.prev = nil // avoid memory leaks
    e.list = nil
    l.len--
    return e
}

// Remove removes e from l if e is an element of list l.
// It returns the element value e.Value.
func (l *List) Remove(e *Element) interface{} {
    if e.list == l {
        // if e.list == l, l must have been initialized when e was inserted
        // in l or l == nil (e is a zero Element) and l.remove will crash
        l.remove(e)
    }
    return e.Value
} 

由源码中可以看到,当执行l.Remove(e)时,会在内部调用l.remove(e)方法删除元素e,为了避免内存泄漏,会将e.next和e.prev赋值为nil,这就是问题根源。

修正程序如下:

package main

import (
    "container/list"
    "fmt"
)

func main() {

    l := list.New()
    l.PushBack(0)
    l.PushBack(1)
    l.PushBack(2)
    l.PushBack(3)
    fmt.Println("original list:")
    prtList(l)

    fmt.Println("deleted list:")
    var next *list.Element
    for e := l.Front(); e != nil; e = next {
        next = e.Next()
        l.Remove(e)
    }

    prtList(l)
}

func prtList(l *list.List) {
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Printf("%v ", e.Value)
    }
    fmt.Printf("\n")
} 

运行程序输出如下:

original list:
0 1 2 3 
deleted list: 

可以看见,list中的所有元素已经被正确删除。

收藏
评论区

相关推荐

golang 中神奇的 slice
声明:本文仅限于简书发布,其他第三方网站均为盗版,原文地址: golang 中神奇的 slice(https://links.jianshu.com/go?tohttps%3A%2F%2Fliqiang.io%2Fpost%2Fimagesliceingolang) 在 golang 中,似乎人们都不太喜欢使用 Linked List,甚至于原
go语言坑之list删除所有元素
go提供了一个list包 类似python的list,可以存储任意类型的数据,并提供了相应的API,如下:type Element func (e Element) Next() Element func (e Element) Prev() Elementtype List func New() List func (l List)
一篇文章彻底弄懂go语言方法的本质
Go 语言不支持经典的面向对象语法元素,比如:类、对象、继承等。但 Go 语言也有方法(method)。和函数相比,Go 语言中的方法在声明形式上仅仅多了一个参数,Go 称之为 receiver 参数。而 receiver 参数正是方法与类型之间的纽带。Go 方法的一般声明形式如下:gofunc (receiver T/T) MethodName(参数列表)
Go! 环境配置和入门
学习一门语言,获得一种思维 开始GO语言的学习之路 #### 环境配置 1\. 下载:https://code.google.com/p/go/downloads/list 根据自己情况下载 2. 安装 3. 将go.exe的路径加到环境变量中:在Path前面加上go.exe的路径 4\. 打开cmd(本人win7环境) 输入go 提示go的
go mod 无法自动下载依赖包的问题
go 11以后启用了go mod功能,用于管理依赖包。 当执行`go mod init`生成`go.mod`文件之后,golang在`运行`、`编译`项目的时候,都会检查依赖并下载依赖包。 在启动了`go mod`之后,通过`go mod`下载的依赖包,不在放在`GOPATH/src`中,而是放到`GOPATH/pkg/mod`中。 比如我当前的`GO
go语言入门【基本类型和语法 一】
GOLANG ------ 入门,废话不说,要知道它是什么,自己百度谷歌就OK的啦 首先,安装环境 ------- 本人的测试环境是centos7 下载编译后版本加入到环境变量即可 GO有个坑的地方是对gopath的设置,类似于JAVA的CLASS\_PATH,但是针对每个项目得重新设置 编译工具 ---- 本人用的编译器是IDEA,对头,就是JA
go遇到的坑
******GO配置:****** ================= GOROOT:go的安装路径:C:\\Go GOPATH:go命令安装的包存放的路径,这里放在%GOROOT%\\gocode GOROOT是不用配置环境变量就可以用的,但是GOPATH要配置才能用,如果没有配置的话默认是在%{USER}%\\go文件夹下。 GOPATH可以配置
Java 中初始化 List 集合的 6 种方式!
![](https://oscimg.oschina.net/oscnet/0db5449d0736e22cb92ba0cf9daad91990a.jpg)   List 是 Java 开发中经常会使用的集合,你们知道有哪些方式可以初始化一个 List 吗?这其中不缺乏一些坑,今天栈长我给大家一一普及一下。   1、常规方式   ListString
Java 中初始化 List 集合的 6 种方式!
![](https://oscimg.oschina.net/oscnet/26618d87-ed40-4f14-b290-16a3e22e57fe.png) List 是 Java 开发中经常会使用的集合,你们知道有哪些方式可以初始化一个 List 吗?这其中不缺乏一些坑,今天栈长我给大家一一普及一下。 1、常规方式 ------ Lis
vs code 下安装golang支持
1)安装gocode go get -u -v github.com/nsf/gocode 2)安装godef go get -u -v github.com/rogpeppe/godef 3)安装golint go get -u -v github.com/golang/lint/golint 4)安装go-find-references g
Guava Lists.transform的一个小坑
    最近在修改项目中bug的时候遇到一个问题,需要修改一个list里的值,但是不管怎么set值,最后序列化的结果都是原来的值。百思不得其解,最后点开返回list的代码,看到里面用了Guava的Lists.transform做了类型转换,才恍然大悟。因为之前听说过Guava的Lists.transform方法有个坑,于是趁机研究下源码。 publ
Kubernetes Client
几乎所有的Controller manager 和CRD Controller 都会使用Client-go 的Informer 函数,这样通过Watch 或者Get List 可以获取对应的Object,下面我们从源码分析角度来看一下Client go Informer 的机制。 kubeClient, err := kubernetes.NewF
List
List toArray(T\[\] a) public Integer[] queryForInts(String sql, Object[] args) throws Exception { List<Integer> list=queryForList(sql,args,new RowMapper<Integer>(){
Scala 谜题
在 Scala 中,List\[String\] 和 List\[Int\] 之间并没有继承关系,但是下面的代码竟然可以通过编译并且顺利运行: object Test extends App { val strList: List\[String\] = List("a", "b", "c") val strToIntList: List\[Int
Tus和go
前言 -- 现如今,分布式文件系统可谓是琳琅满目,多种多样,有hdfs,gfs,zfs,fastdfs,go-fastdfs等,怎么选择合适自己的分布式文件系统呢?在这篇文章中,我们不讲……额,我只想表达一下我在研究go-fastdfs过程中踩的坑。 go-fastdfs ---------- 首先,什么是go-fastdfs?是fastdfs的弟弟吗