首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么我不能使用引用将值附加到结构体的切片中?

为什么我不能使用引用将值附加到结构体的切片中?
EN

Stack Overflow用户
提问于 2019-12-01 10:59:41
回答 3查看 681关注 0票数 0

我假设切片是通过引用传递的,但这似乎对值有效,但对数组本身无效。例如,如果我有这样的结构:

代码语言:javascript
运行
复制
    l := Line{
        Points: []Point{
            Point{3, 4},
        },
    }

我可以定义一个变量,它会传递一个对结构切片的引用

代码语言:javascript
运行
复制
slice := l.Points

然后,如果我修改它,变量引用的原始结构将反映这些修改。

代码语言:javascript
运行
复制
slice[0].X = 1000
fmt.Printf(
    "This value %d is the same as this %d", 
    slice[0].X, 
    l.Points[0].X,
)

这与数组的行为不同,我假设数组是通过值传递的。因此,例如,如果我使用数组定义了前面的代码:

代码语言:javascript
运行
复制
l := Line{
    Points: [1]Point{
        Point{3, 4},
    },
}
arr := l.Points
arr[0].X = 1000
fmt.Println(arr.[0].X != s.Points[0].X) // equals true, original struct is untouched

那么,l结构就不会被修改。

现在,如果我想修改切片本身,我显然不能这样做:

代码语言:javascript
运行
复制
slice = append(slice, Point{99, 100})

因为这只会重新定义切片变量,从而丢失原始引用。我知道我可以简单地这样做:

代码语言:javascript
运行
复制
l.Points = append(l.Points, Point{99, 100})

但是,在某些情况下,使用另一个变量会更方便,而不必键入整个变量。

我试过这个:

代码语言:javascript
运行
复制
*slice = append(*slice, Point{99, 100})

但它不起作用,因为我试图取消引用显然不是指针的对象。

我最终尝试了一下:

代码语言:javascript
运行
复制
slice := &l.Points

*slice = append(l.Points, Point{99, 100})

它起作用了,但我不确定发生了什么。为什么slice的值没有被覆盖?append在这里是如何工作的?

EN

回答 3

Stack Overflow用户

发布于 2019-12-01 11:33:14

Append返回一个新的切片,它可能会修改初始切片的原始后备数组。原始切片仍将指向原始支持数组,而不是新的支持数组(它可能在内存中的相同位置,也可能不在内存中的相同位置)

例如(playground)

代码语言:javascript
运行
复制
slice := []int{1,2,3}
fmt.Println(len(slice))
// Output: 3
newSlice := append(slice, 4)
fmt.Println(len(newSlice))
// Output: 4
fmt.Println(len(slice))
// Output: 3 

虽然片可以被描述为“指向数组的胖指针”,但它不是指针,因此您不能取消对它的引用,这就是为什么会出现错误的原因。

通过创建一个指向切片的指针,并像上面那样使用append,您可以将指针所指向的切片设置为由append返回的“新”切片。

有关更多信息,请查看Go Slice Usage And Internals

票数 2
EN

Stack Overflow用户

发布于 2019-12-01 11:40:18

第一次尝试没有成功,因为切片不是指针,它们可以被认为是引用类型。Append将修改底层数组,如果它有足够的容量,否则它会返回一个新的切片。

你可以通过两次尝试的组合来实现你想要的。

playground

代码语言:javascript
运行
复制
l := Line{
    Points: []Point{
        Point{3, 4},
    },
}

slice := &l.Points

for i := 0; i < 100; i++ {
    *slice = append(*slice, Point{99 + i, 100 + i})
}

fmt.Println(l.Points)
票数 0
EN

Stack Overflow用户

发布于 2019-12-04 02:58:09

我知道这可能是亵渎神明的,但对我来说,将切片看作结构是有用的。

代码语言:javascript
运行
复制
type Slice struct {
    len int
    cap int
    Array *[n]T // Pointer to array of type T
} 

因为在像C这样的语言中,[]运算符也是一个解引用运算符,所以我们可以认为每次访问切片时,我们实际上是在解引用底层数组并为其赋值。这就是:

代码语言:javascript
运行
复制
var s []int
s[0] = 1

可能被认为等同于(在伪代码中):

代码语言:javascript
运行
复制
var s Slice
*s.Array[0] = 1 

这就是为什么我们可以说切片是“指针”。出于这个原因,它可以像这样修改它的底层数组:

代码语言:javascript
运行
复制
myArray := [3]int{1,1,1}
mySlice := myArray[0:1]
mySlice = append(mySlice, 2, 3) // myArray == mySlice

修改mySlice也会修改myArray,因为切片存储了指向数组的指针,并且在追加时,我们将取消对该指针的引用。

然而,这种行为并不总是这样的。如果超出了原始数组的容量,则会创建一个新数组,而原始数组保持不变。

代码语言:javascript
运行
复制
myArray := [3]int{1,1,1}
mySlice := myArray[0:1]
mySlice = append(mySlice, 2, 3, 4, 5) // myArray != mySlice

当我们试图将切片本身视为实际的指针时,就会产生混淆。因为我们可以通过附加到底层数组来修改它,所以我们相信在这种情况下:

代码语言:javascript
运行
复制
sliceCopy := mySlice
sliceCopy = append(sliceCopy, 6)

两个切片,slicesliceCopy是相同的,但它们不是。我们必须显式地传递对片的内存地址的引用(使用&操作符)才能修改它。这就是:

代码语言:javascript
运行
复制
sliceAddress := &mySlice
*sliceAddress = append(mySlice, 6) // or append(*sliceAddress, 6)

另请参阅

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59122177

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档