【问题标题】:How can a slice contain itself?切片如何包含自身?
【发布时间】:2016-03-18 06:26:09
【问题描述】:

我正在尝试使用“Go 编程语言”来学习 Golang,并且我已经到达了切片部分。他们在数组和切片之间进行比较,因为两个数组可以与两个切片不能比较的== 进行比较。正文内容如下:

"== operator for arrays of strings, it may be puzzling that slice
comparisons do not also work this way. There are two reasons why deep 
equivalence is problematic. First, unlike array elements, the elements
of a slice are indirect, making it possible for a slice to contain 
itself. Although there are ways to deal with such cases, none is 
simple, efficient, and most importantly, obvious."

由于元素是间接的,切片可能包含自身是什么意思?

【问题讨论】:

    标签: arrays go comparison slice


    【解决方案1】:

    包含自身的切片

    除了递归类型(例如type Foo []Foo,参见ANisus 的回答)除了演示之外没有任何用处,如果切片的元素类型是interface{},则切片可能包含自身:

    s := []interface{}{"one", nil}
    s[1] = s
    

    在这个例子中,切片 s 将有 2 个接口值,第一个“包装”一个简单的字符串 "one",另一个接口值包装切片值本身。创建接口值时,将包装该值的副本,如果是切片,则表示切片头/描述符的副本,其中包含指向底层数组的指针,因此该副本将具有指向的相同指针值相同的底层数组。 (有关接口表示的更多详细信息,请参阅The Laws of Reflection: The representation of an interface。)

    如果你很快就打印出来:

    fmt.Println(s)
    

    你会得到一个致命错误,比如:

    runtime: goroutine stack exceeds 250000000-byte limit
    fatal error: stack overflow
    

    因为fmt.Println() 尝试递归地打印内容,并且由于第二个元素是一个切片,它指向正在打印的切片的同一数组,所以它会陷入无限循环。

    另一种判断它是否真的是切片本身的方法:

    s := []interface{}{"one", nil}
    s[1] = s
    fmt.Println(s[0])
    
    s2 := s[1].([]interface{})
    fmt.Println(s2[0])
    
    s3 := s2[1].([]interface{})
    fmt.Println(s3[0])
    

    输出(在Go Playground上试试):

    one
    one
    one
    

    无论我们走多远,第二个元素始终是指向与s 相同的数组的切片值,包裹在interface{} 值中。

    间接起到重要作用,因为副本将被包装在interface{} 中,但副本将包含相同的指针。

    数组不能包含自身

    将类型改为数组:

    s := [2]interface{}{"one", nil}
    s[1] = s
    fmt.Println(s[0])
    
    s2 := s[1].([2]interface{})
    fmt.Println(s2[0])
    
    s3 := s2[1].([2]interface{})
    fmt.Println(s3[0])
    

    输出(在Go Playground上试试):

    one
    one
    panic: interface conversion: interface is nil, not [2]interface {}
    

    这是因为当数组被包装成interface{} 时,一个副本将被包装 - 并且副本不是原始数组。所以s 将有第二个值,一个包装数组的interface{},但这是一个不同的数组,其第二个值未设置,因此将是nilinterface{} 类型的零值),所以尝试“进入”这个数组会恐慌,因为它是 niltype assertion 失败,因为没有使用特殊的“逗号,ok”形式)。

    由于这个s 数组不包含自身,一个简单的fmt.Println() 将显示其全部内容:

    fmt.Println(s)
    

    输出:

    [one [one <nil>]]
    

    进一步interface{}包装分析

    如果将数组包裹在interface{} 中并修改原始数组的内容,则interface{} 中包裹的值不受影响:

    arr := [2]int{1, 2}
    var f interface{} = arr
    arr[0] = 11
    
    fmt.Println("Original array:    ", arr)
    fmt.Println("Array in interface:", f)
    

    输出:

    Original array:     [11 2]
    Array in interface: [1 2]
    

    如果你对切片做同样的事情,包裹的切片(因为指向同一个底层数组)也会受到影响:

    s := []int{1, 2}
    f = s
    s[0] = 11
    
    fmt.Println("Original slice:    ", s)
    fmt.Println("Slice in interface:", f)
    

    输出:

    Original slice:     [11 2]
    Slice in interface: [11 2]
    

    Go Playground 上试试这些。

    【讨论】:

    • 很好的解释。这本书写了这么无聊的评论,需要这么多的拆包。
    • 感谢您的解释。但我仍然不明白,我认为提问的人没有将其作为答案。所以,让我说一下为什么,仅仅是因为这本书没有在这句话之前介绍接口:)
    【解决方案2】:

    以下示例创建一个包含自身的切片:

    type Foo []Foo  
    bar := make(Foo, 1)
    bar[0] = bar
    

    可以这样做是因为切片值内部包含指向数组的指针、长度和容量。

    另一方面,数组是一个值。它最多只能包含指向自身的指针。

    【讨论】:

    • 这个例子真的很有趣,它让我对 go slice 类型思考了很多。为什么要投反对票? @downvoter
    • @H.W.Du :) 递归类型有时肯定很有用。也许不在这个例子中,但仍然如此。我以为downvoter只是不知道他们。
    【解决方案3】:

    切片包含指向保存元素的内存的指针、可用元素计数的长度以及内存大小的能力。就像这样:

    typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
    

    我认为是indirect,因为元素是由指针引用的。 当然,我们可以在void *data 中拥有切片本身。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-21
      • 2012-05-16
      • 1970-01-01
      • 2017-07-08
      • 2016-10-27
      • 1970-01-01
      • 2021-11-09
      • 2012-09-26
      相关资源
      最近更新 更多