包含自身的切片
除了递归类型(例如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{},但这是一个不同的数组,其第二个值未设置,因此将是nil(interface{} 类型的零值),所以尝试“进入”这个数组会恐慌,因为它是 nil(type 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 上试试这些。