轉自http://sharecore.net/blog/2014/01/09/the-trap-in-golang-list/
一直想不清楚一個問題,簡單設計的東西到底是“坑多”還是“坑少”呢? 復雜的設計,考慮的太全面,使用起來更麻煩,使用者容易陷入亂,落入自身的陷阱;而簡單的設計呢,在許多方面上又顧及不周,如果使用者對其“設計”沒仔細研究,或者其實現本身又是一個黑盒子,也容易掉入到設計本身遺留下來的“陷阱”。下面是我剛開始使用Go寫代碼時碰到的一個小“坑”,這個“坑”的原因我歸結為后者。
這個“小坑”來自於go的container/list package的使用上。導致“坑”的代碼大概如下所示:
package main import ( "container/list" "fmt" ) func main() { //初始化一個list l := list.New() l.PushBack(1) l.PushBack(2) l.PushBack(3) l.PushBack(4) fmt.Println("Before Removing...") //遍歷list,刪除元素 for e := l.Front(); e != nil; e = e.Next() { fmt.Println("removing", e.Value) l.Remove(e) } fmt.Println("After Removing...") //遍歷刪除完元素后的list for e := l.Front(); e != nil; e = e.Next() { fmt.Println(e.Value) } }
以上代碼很簡單,按常理來看,應該能得到正確的結果,list最后將會被清空。可事實卻完全不是這樣,執行后結果如下:
Before Removing... removing 1 After Removing... 2 3 4
從結果可以看出,list根本沒有清空,而只是刪除了第一個元素。這是為何?原因就在container/list package的實現上了。這應該是我見過的實現最簡單list了,出去注釋也就100來行實現代碼,而且它不只是一個簡單鏈表,而且可以當做stack,當做queue來使用。
下面是Remove方法的代碼:
// 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 }
這下問題原因就明顯了,就出現在e.next = nil 這行代碼上。當執行玩remove,e.next就變成了nil,list遍歷當然也就終止了。找出問題的原因,我們就容易找到workaround的辦法了,將e.next用中間變量保存起來就OK了,代碼如下:
package main import ( "container/list" "fmt" ) func main() { l := list.New() l.PushBack(1) l.PushBack("asd") l.PushBack(3) l.PushBack(4) fmt.Println("Before Removing...") var n *list.Element for e := l.Front(); e != nil; e = n { fmt.Println("removing", e.Value) n = e.Next() l.Remove(e) } fmt.Println("After Removing...") for e := l.Front(); e != nil; e = e.Next() { fmt.Println(e.Value) } }
正常結果輸出:
Before Removing... removing 1 removing 2 removing 3 removing 4 After Removing...
