从切片中删除元素时出现意外结果
Unexpected result when removing an element from a slice
我从切片中移除第一个元素时遇到了意想不到的结果,这是我的测试代码,希望能帮助你理解我的困惑
定义结构
type A struct {
member int
}
func (a A) String() string {
return fmt.Sprintf("%v", a.member)
}
type B struct {
a A
aPoint *A
}
func (b B) String() string {
return fmt.Sprintf("a: %v, aPoint: %v", b.a, b.aPoint)
}
下面是我的测试用例
func TestSliceRemoveFirstEle(t *testing.T) {
demo := []B{
{a: A{member: 1}},
{a: A{member: 2}},
{a: A{member: 3}},
}
for i := range demo {
demo[i].aPoint = &demo[i].a
}
fmt.Println("demo before operation is ", demo) // result: demo before operation is [a: 1, aPoint: 1 a: 2, aPoint:2 a: 3, aPoint: 3]
demo = append(demo[:0], demo[1:]...)
fmt.Println("demo after operation is ", demo) // result: demo after operation is [a: 2, aPoint: 3 a: 3, aPoint: 3]
}
我的预期结果是 [a: 2, aPoint: 2 a: 3, aPoint: 3]
当我使用另一种方式向切片添加元素时,效果很好。
func TestAddSliceRemoveFirstEle(t *testing.T) {
demo := make([]B, 0, 3)
a1 := A{member: 1}
a2 := A{member: 2}
a3 := A{member: 3}
demo = append(demo, B{a: a1, aPoint: &a1}, B{a: a2, aPoint: &a2}, B{a: a3, aPoint: &a3})
fmt.Println("demo before operation is ", demo) // result: demo before operation is [a: 1, aPoint: 1 a: 2, aPoint: 2 a: 3, aPoint: 3]
demo = append(demo[:0], demo[1:]...)
fmt.Println("demo after operation is ", demo) // result: demo after operation is [a: 2, aPoint: 2 a: 3, aPoint: 3]
}
我对结果感到困惑,第一种情况从切片中删除元素后发生了什么,这两种实现之间有什么区别?
本质上 aPoint
指针仍然指向相同的地址,但这些地址的值发生了变化。
附加后是 demo[0].aPoint == &demo[1].a
而 不是 demo[0].aPoint == &demo[0].a
.
https://play.golang.org/p/HoNhFlxTEFN
slice expression demo[:0]
将产生一个长度为 0
的新切片,它指向与 demo
切片相同的底层数组。该数组中存储的元素仍然存在,它们没有被丢弃。
所以在 demo[:0]
之后,底层数组仍然是:
[
B{a:1,<pointer_to_idx:0_field_a>},
B{a:2,<pointer_to_idx:1_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
]
demo[1:]...
丢弃第一个元素并将剩余的两个元素传递给 append
。然后 append
获取这两个元素并更新底层数组的前两个元素。之后底层数组将如下所示:
[
B{a:2,<pointer_to_idx:1_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
]
请注意,现在数组第 0 个元素的 aPointer
字段指向第 1 个元素的 a
字段。并且第一个元素的 aPointer
字段指向第二个元素的 a
字段。
在您的第一个示例中,所有 A 结构仅存在于切片中,并且指针指向切片的元素。
在您的第二个示例中, A 结构存在于切片之外,因此没有针对切片元素的指针。
根据 https://golang.org/ref/spec#Appending_and_copying_slices
的 golang 附加规范
If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.
由于我们正在减小底层数组的大小,因此我们正在重用它。
这是容量为 3 的底层数组在重新切片之前和之后的样子
123 - before
233 - after
如您的示例所示,之前指向“2”的内容现在指向“3”,通过这个操场可以更简单地查看
package main
import (
"fmt"
)
func main() {
mySlice := []int{1, 2, 3}
one := &mySlice[0]
two := &mySlice[1]
three := &mySlice[2]
fmt.Printf("%v-%d\n%v-%d\n%v-%d\n",one,*one,two,*two,three,*three)
fmt.Printf("%v cap %d\n",mySlice, cap(mySlice))
mySlice = append(mySlice[:1],mySlice[2:]...)
fmt.Printf("%v cap %d\n",mySlice, cap(mySlice))
fmt.Printf("%v-%d\n%v-%d\n%v-%d\n",one,*one,two,*two,three,*three)
}
你会在哪里看到
0xc0000be000-1
0xc0000be008-2
0xc0000be010-3
[1 2 3] cap 3
[1 3] cap 3
0xc0000be000-1
0xc0000be008-3
0xc0000be010-3
简而言之,从切片的元素中取出指针并重新切片总是会导致此类错误。
根据此处的脚注,它还可能导致内存泄漏问题 https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order
我从切片中移除第一个元素时遇到了意想不到的结果,这是我的测试代码,希望能帮助你理解我的困惑 定义结构
type A struct {
member int
}
func (a A) String() string {
return fmt.Sprintf("%v", a.member)
}
type B struct {
a A
aPoint *A
}
func (b B) String() string {
return fmt.Sprintf("a: %v, aPoint: %v", b.a, b.aPoint)
}
下面是我的测试用例
func TestSliceRemoveFirstEle(t *testing.T) {
demo := []B{
{a: A{member: 1}},
{a: A{member: 2}},
{a: A{member: 3}},
}
for i := range demo {
demo[i].aPoint = &demo[i].a
}
fmt.Println("demo before operation is ", demo) // result: demo before operation is [a: 1, aPoint: 1 a: 2, aPoint:2 a: 3, aPoint: 3]
demo = append(demo[:0], demo[1:]...)
fmt.Println("demo after operation is ", demo) // result: demo after operation is [a: 2, aPoint: 3 a: 3, aPoint: 3]
}
我的预期结果是 [a: 2, aPoint: 2 a: 3, aPoint: 3]
当我使用另一种方式向切片添加元素时,效果很好。
func TestAddSliceRemoveFirstEle(t *testing.T) {
demo := make([]B, 0, 3)
a1 := A{member: 1}
a2 := A{member: 2}
a3 := A{member: 3}
demo = append(demo, B{a: a1, aPoint: &a1}, B{a: a2, aPoint: &a2}, B{a: a3, aPoint: &a3})
fmt.Println("demo before operation is ", demo) // result: demo before operation is [a: 1, aPoint: 1 a: 2, aPoint: 2 a: 3, aPoint: 3]
demo = append(demo[:0], demo[1:]...)
fmt.Println("demo after operation is ", demo) // result: demo after operation is [a: 2, aPoint: 2 a: 3, aPoint: 3]
}
我对结果感到困惑,第一种情况从切片中删除元素后发生了什么,这两种实现之间有什么区别?
本质上 aPoint
指针仍然指向相同的地址,但这些地址的值发生了变化。
附加后是 demo[0].aPoint == &demo[1].a
而 不是 demo[0].aPoint == &demo[0].a
.
https://play.golang.org/p/HoNhFlxTEFN
slice expression demo[:0]
将产生一个长度为 0
的新切片,它指向与 demo
切片相同的底层数组。该数组中存储的元素仍然存在,它们没有被丢弃。
所以在 demo[:0]
之后,底层数组仍然是:
[
B{a:1,<pointer_to_idx:0_field_a>},
B{a:2,<pointer_to_idx:1_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
]
demo[1:]...
丢弃第一个元素并将剩余的两个元素传递给 append
。然后 append
获取这两个元素并更新底层数组的前两个元素。之后底层数组将如下所示:
[
B{a:2,<pointer_to_idx:1_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
B{a:3,<pointer_to_idx:2_field_a>},
]
请注意,现在数组第 0 个元素的 aPointer
字段指向第 1 个元素的 a
字段。并且第一个元素的 aPointer
字段指向第二个元素的 a
字段。
在您的第一个示例中,所有 A 结构仅存在于切片中,并且指针指向切片的元素。 在您的第二个示例中, A 结构存在于切片之外,因此没有针对切片元素的指针。
根据 https://golang.org/ref/spec#Appending_and_copying_slices
的 golang 附加规范If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.
由于我们正在减小底层数组的大小,因此我们正在重用它。 这是容量为 3 的底层数组在重新切片之前和之后的样子
123 - before
233 - after
如您的示例所示,之前指向“2”的内容现在指向“3”,通过这个操场可以更简单地查看
package main
import (
"fmt"
)
func main() {
mySlice := []int{1, 2, 3}
one := &mySlice[0]
two := &mySlice[1]
three := &mySlice[2]
fmt.Printf("%v-%d\n%v-%d\n%v-%d\n",one,*one,two,*two,three,*three)
fmt.Printf("%v cap %d\n",mySlice, cap(mySlice))
mySlice = append(mySlice[:1],mySlice[2:]...)
fmt.Printf("%v cap %d\n",mySlice, cap(mySlice))
fmt.Printf("%v-%d\n%v-%d\n%v-%d\n",one,*one,two,*two,three,*three)
}
你会在哪里看到
0xc0000be000-1
0xc0000be008-2
0xc0000be010-3
[1 2 3] cap 3
[1 3] cap 3
0xc0000be000-1
0xc0000be008-3
0xc0000be010-3
简而言之,从切片的元素中取出指针并重新切片总是会导致此类错误。 根据此处的脚注,它还可能导致内存泄漏问题 https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order