0

我从 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]
}

我对结果感到困惑,从切片中删除元素后第一种情况发生了什么,这两种实现之间有什么区别?

4

2 回答 2

0

本质上,aPoint指针仍然指向相同的地址,但这些地址的值发生了变化。

在追加之后是demo[0].aPoint == &demo[1].a不是 demo[0].aPoint == &demo[0].a

https://play.golang.org/p/HoNhFlxTEFN


切片表达式 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>},
]

请注意,现在,aPointer数组的第 0 个元素的字段指向第一个元素的a字段。第一个元素的字段aPointer指向第二个元素的a字段。

于 2021-08-08T05:29:00.517 回答
0

在您的第一个示例中,所有 A 结构仅存在于切片中,并且指针指向切片的元素。在您的第二个示例中, A 结构存在于切片之外,因此没有指向切片元素的指针。

根据https://golang.org/ref/spec#Appending_and_copying_slices上的 golang 附加规范

如果 s 的容量不足以容纳附加值,则 append 分配一个新的、足够大的底层数组,该数组既适合现有的切片元素又适合附加值。否则,追加重用底层数组。

由于我们正在减小底层数组的大小,因此我们正在重用它。这是容量 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

于 2021-08-08T05:32:54.950 回答