【问题标题】:Is there a bug in handling slices with references in Go?在 Go 中处理带有引用的切片是否存在错误?
【发布时间】:2020-07-15 17:44:46
【问题描述】:

我正在尝试构建一个新的结构列表,其中包含对另一个切片中存在的项目的引用。看到了就更容易理解了,所以我准备了一个可以运行的sn-p。 我有一个包含两个点(笛卡尔坐标)的列表(dummylist),我想解析它以构建一个新列表(mylist),其中包含具有某些特征的项目(在示例中,X > 80)。我定义了两点:{X:90.0, Y:50.0} 和 {X:20.0 , Y:30.0}。我希望 mylist 将包含 {X:90.0, Y:50.0},而不是最后有 {X:20.0, Y:30.0}。通过到处打印,我可以验证算法是否正常工作(它在正确的情况下进入“if”条件),但最后,“mylist”包含错误的元素。

package main
import(
    "fmt"
)

func main() {

type point struct {
    X float64
    Y float64
}

type pointsList []point

type pointContainer struct {
    Point *point
}

type pointContainerList []pointContainer

// Prepare a slice with two elements
dummylist := new(pointsList)
*dummylist = append(*dummylist, point{X:90.0, Y:50.0})
*dummylist = append(*dummylist, point{X:20.0 , Y:30.0})

// My empty list
mylist := new(pointContainerList)

fmt.Println(fmt.Sprintf("---- At the beginning, mylist contains %d points", len(*mylist)))

// Filter the initial list to take only elements
for _, pt := range *dummylist {
    fmt.Println("\n---- Evaluating point ", pt)

    if pt.X > 80 {
        fmt.Println("Appending", pt)
        *mylist = append(*mylist, pointContainer{Point: &pt})
        fmt.Println("Inserted point:", (*mylist)[0].Point, "len = ", len(*mylist))
    }
}

// mylist should contain {X:90.0, Y:50.0}, instead...
fmt.Println(fmt.Sprintf("\n---- At the end, mylist contains %d points", len(*mylist)))
fmt.Println("Content of mylist:", (*mylist)[0].Point)
}

在这里你可以运行代码: https://play.golang.org/p/AvrC3JJBLdT

一些有用的考虑: 我已经通过多个测试看到,最后, mylist 包含循环中最后一个解析的项目。我觉得引用有问题。就像列表中插入的项目(在第一次迭代中)取决于其他迭代的“pt”。相反,如果我使用索引(for i, pt := range *dummylist(*dummylist)[i]),一切正常。

在谈论 Golang 中的错误之前……我是否遗漏了什么?

【问题讨论】:

  • 与此问题的问题原因相同:stackoverflow.com/questions/62910940/…(答案在评论中)
  • 简而言之,pt 在每次迭代中重用,因此表达式 &ptalways 评估为相同的值,即相同的指针指向相同的内存地址,因此容器列表中的每个元素都是指向单个 pt 值的指针的副本,在循环结束时,它将始终包含最后一个元素切片/数组的迭代次数。
  • ... 只需在迭代块的顶部执行 pt := pt 即可解决您的问题。

标签: go


【解决方案1】:

是的,你错过了一些东西。在这一行:

*mylist = append(*mylist, pointContainer{Point: &pt})

您将循环变量&pt 的地址放入您的结构中。随着循环的继续,pt 的值会发生变化。 (或者换句话说,&pt 将是循环的每次迭代的相同指针)。

来自go language specification

...

迭代值分配给各自的迭代 赋值语句中的变量。

迭代变量可以由“range”子句使用 短变量声明 (:=) 的形式。在这种情况下,它们的类型是 设置为各个迭代值的类型,它们的范围是 “for”语句块;它们在每次迭代中重复使用。 如果迭代变量是在“for”语句之外声明的, 执行后,它们的值将是最后一次迭代的值。

一种解决方案是创建一个新值,但我不确定您从这么多指针中获得了什么:[]point 可能比指向切片的指针更有效(并且更不容易出错)指向points 的指针结构。

【讨论】:

  • 我对 Go 几乎一无所知,但我想知道为什么会有这么多指针;感觉太像 C 了,我认为/希望所有这些新语言都试图帮助取代它 ;-)
  • 除非您有充分的理由考虑超出问题范围,否则我不会使用指针编写此代码,因为别名问题可能会再次困扰您。这是我的代码版本:play.golang.org/p/9WlLoSmQmMK
  • 是的,它们的范围仅限于 for 语句的块:我添加了对规范的引用。
  • @PaulHankin 不,你的例子有点不同。 mylist 不应该是点列表。一个简单的修复可以是play.golang.org/p/3ZgoRJS65Z-
  • 我的示例展示了我如何不使用这么多额外的类型和指针来实现与您的代码相同的结果。如果你有充分的理由使用这么多类型、容器结构、指针、指向切片的指针等等,那么你可以忽略,但我觉得这些建议对你很有用。
猜你喜欢
  • 2015-01-30
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
  • 2016-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多