一个切片如何包含自身?

huangapple go评论86阅读模式
英文:

How can a slice contain itself?

问题

在这段文本中,提到了切片(slices)和数组(arrays)之间的比较。对于数组,可以使用==运算符进行比较,但对于切片,不能这样比较。文本中提到了两个原因,解释了为什么深度等价性(deep equivalence)在切片中是有问题的。

第一个原因是,与数组元素不同,切片的元素是间接的(indirect),这意味着切片可以包含自身。这种情况下,处理起来并不简单、高效,而且最重要的是,并不明显。

换句话说,切片中的元素可以是其他切片,而这些切片又可以包含其他切片,最终可能形成一个循环引用的结构,即切片包含了自身。这种情况下,判断两个切片是否相等就变得复杂和困难。

所以,文本中提到的"it's possible for a slice to contain itself due to the elements being indirect"的意思是,切片的元素可以是其他切片,从而可能导致切片包含了自身。

英文:

I'm trying to learn Golang using "The Go Programming Language" and I've reached the section on slices. They make the comparison between arrays and slices in that two arrays can be compared with == where two slices can not. The text reads as the following:

"== 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."

What is meant by it's possible for a slice to contain itself due to the elements being indirect?

答案1

得分: 22

包含自身的切片

除了递归类型(例如 type Foo []Foo,参见ANisus的答案)之外,如果切片的元素类型是 interface{},则切片可以包含自身:

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

在这个例子中,切片 s 将有两个接口值,第一个接口值“包装”了一个简单的字符串 "one",另一个接口值包装了切片值本身。当创建一个接口值时,将包装值的副本,对于切片来说,这意味着切片头/描述符的副本,其中包含指向底层数组的指针,因此副本将具有指向相同底层数组的相同指针值。(有关接口表示的更多细节,请参见反射定律:接口的表示

如果你尝试快速打印它:

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{},但这是一个不同的数组,其第二个值未设置,因此将是 nil(类型 interface{} 的零值),因此尝试“进入”此数组将会引发 panic,因为它是 nil类型断言失败,因为没有使用特殊的“comma, 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上尝试这些代码。

英文:

Slice containing itself

Besides a recursive type (such as type Foo []Foo, see ANisus's answer) which is good for nothing besides demonstration, a slice may contain itself if for example the element type of the slice is interface{}:

s := []interface{}{&quot;one&quot;, nil}
s[1] = s

In this example the slice s will have 2 interface values, the first "wrapping" a simple string &quot;one&quot;, and another interface value wrapping the slice value itself. When an interface value is created, a copy of the value will be wrapped which in case of slices means a copy of the slice header/descriptor, which contains the pointer to the underlying array, so the copy will have the same pointer value pointing to the same underlying array. (For more details about the representation of interfaces, see The Laws of Reflection: The representation of an interface.)

If you were quickly on to print it:

fmt.Println(s)

You would get a fatal error, something like:

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

Because fmt.Println() tries to print the content recursively, and since the 2nd element is a slice pointing to the same array of the the slice being printed, it runs into an infinite loop.

Another way to see if it really is the slice itself:

s := []interface{}{&quot;one&quot;, nil}
s[1] = s
fmt.Println(s[0])

s2 := s[1].([]interface{})
fmt.Println(s2[0])

s3 := s2[1].([]interface{})
fmt.Println(s3[0])

Output (try it on the Go Playground):

one
one
one

No matter how deep we go, the 2nd element will always be the slice value pointing to the same array as s, wrapped in an interface{} value.

The indirection plays the important role as a copy will be wrapped in the interface{} but the copy will contain the same pointer.

Array can't contain itself

Changing the type to be an array:

s := [2]interface{}{&quot;one&quot;, 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])

Output (try it on the Go Playground):

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

This is because when the array is wrapped into an interface{}, a copy will be wrapped - and a copy is not the original array. So s will have a second value, an interface{} wrapping an array, but that is a different array whose 2nd value is not set and therefore will be nil (the zero value of type interface{}), so attempting to "go into" this array will panic because it is nil (type assertion fails because not the special "comma, ok" form was used).

Since this s array does not contain itself, a simple fmt.Println() will reveal its full content:

fmt.Println(s)

Output:

[one [one &lt;nil&gt;]]

Further interface{} wrapping analysis

If you wrap an array in an interface{} and modify the content of the original array, the value wrapped in the interface{} is not affected:

arr := [2]int{1, 2}
var f interface{} = arr
arr[0] = 11

fmt.Println(&quot;Original array:    &quot;, arr)
fmt.Println(&quot;Array in interface:&quot;, f)

Output:

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

If you do the same with a slice, the wrapped slice (since points to the same underlying array) is also affected:

s := []int{1, 2}
f = s
s[0] = 11

fmt.Println(&quot;Original slice:    &quot;, s)
fmt.Println(&quot;Slice in interface:&quot;, f)

Output:

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

Try these on the Go Playground.

答案2

得分: 8

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

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

这是因为切片值在内部包含一个指向数组的指针,一个长度和一个容量。

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

英文:

The below example creates a slice that contains itself:

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

This can be done because the slice value internally contains a pointer to an array, a length, and a capacity.

An array on the other hand is a value. It can, at best, contain pointers to itself.

答案3

得分: 0

一个切片包含一个指向存储元素的内存的指针,一个可用元素数量的长度,以及内存的容量大小。所以它的定义如下:

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

我认为它是“间接的”,因为元素是通过指针引用的。
当然,我们也可以将切片本身存储在void *data中。

英文:

a slice contains a pointer to the memory holding the elements, a length for available elements count, and a capability for how big the memory. so it like:

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

I think it is indirect, because the elements are referenced by pointer.
and of course we can have the slice itself in void *data.

huangapple
  • 本文由 发表于 2016年3月18日 14:26:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/36077566.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定