值拷贝导致使用container/list出现的诡异问题分析

golang值拷贝导致使用container/list出现的诡异问题分析

先看正确使用list的两种方式

  • 使用list.New()

    package main
    
    import (
        "container/list"
        "fmt"
    )
    
    func main() {
        lPtr := list.New()
        lPtr.PushBack(1)
        for front := lPtr.Front(); front != nil; front = front.Next() {
            fmt.Println(front.Value)
        }
    }

    输出

  • 使用list.List{}
package main

import (
   "container/list"
   "fmt"
)

func main() {
   l := list.List{}
   l.PushBack(1)
   for front := l.Front(); front != nil; front = front.Next() {
      fmt.Println(front.Value)
   }
}

输出

list的错误使用方式

package main

import (
   "container/list"
   "fmt"
)

func main() {
   l := *list.New()
   l.PushBack(1)
   for front := l.Front(); front != nil; front = front.Next() {
      fmt.Println(front.Value)
   }
}

输出


错误原因分析

为什么 *list.New()
方式给 l
后,放进 l
中的元素打印不出来了呢?

让我们来看看l.Front()中的逻辑, 如果 len
不为 0
,返回的是 l.root.next

func (l *List) Front() *Element {
    if l.len == 0 {
        return nil
    }
    return l.root.next
}

上面打印list的循环条件是 front != nil
,打印不出元素,也就说明 l.root.next==nil

那我们把 l.root.next
的地址打印出来看看, 当 len!=0
时才会返回 l.root.next
,我们要先添加一个元素

package main

import (
   "container/list"
   "fmt"
)

func main() {
   lPtr := list.New()
   lPtr.PushBack("ptr1")
   fmt.Printf("lPtr=%p  lPtr.root.next=%p\n", lPtr, lPtr.Front())
   l := *lPtr
   l.PushBack(1)
   fmt.Printf("&l=%p  l.root.next=%p\n", &l, l.Front())
   for front := l.Front(); front != nil; front = front.Next() {
      fmt.Println(front.Value)
   }
}

输出

lPtr=0xc000084150  lPtr.root.next=0xc000084180
&l=0xc0000841b0  l.root.next=0xc000084180
ptr1
1

我们看到 l.root.next
的值 lPtr.root.next
的值都是 0xc000084180
, 问题就出在这里

lPtr
添加元素 "ptr1"
后, lPtr
内部链表如下

lPtr.root.prev->Element{"ptr1"}->&lPtr.root  //Element{"ptr1"}地址:0xc000084180
lPtr.root.next->Element{"ptr1"}->&lPtr.root

l := *lPtr
时,内部属性的值拷贝如下:

l.root.prev->Element{"ptr1"}->&lPtr.root  //Element{"ptr1"}地址:0xc000084180
l.root.next->Element{"ptr1"}->&lPtr.root

l
添加元素 "1"
后, l
内部链表如下

l.root.prev->Element{"1"}->Element{"ptr1"}->&lPtr.root
l.root.next->Element{"ptr1"}->Element{"1"}->&lPtr.root

至此就是弄清楚了上面发生怪异输出结果的原因了。
注意:在go中尽量避免对复杂对象做简单的值传递赋值。