【问题标题】:Golang: help understanding pointers, assignment, and unexpected behaviorGolang:帮助理解指针、赋值和意外行为
【发布时间】:2017-05-20 15:13:15
【问题描述】:

所以我回来了更多我似乎无法解决的初学者问题。 我正在试验以下代码。

func main() {
start := time.Now()
var powers []*big.Int
for i := 1; i < 1000; i++ {
    I := big.NewInt(int64(i))
    I.Mul(I, I)
    powers = append(powers, I)
}
fmt.Println(powers)
fmt.Println(time.Since(start))
start = time.Now()
var seqDiffs []*big.Int
diff := new(big.Int)
for i, v := range powers {
    if i == len(powers)-2 {
        break
    }
    diff = v.Sub(powers[i+1], v)
    seqDiffs = append(seqDiffs, diff)
}
fmt.Println(seqDiffs)
fmt.Println(time.Since(start))
}

我的意图是通过以下方式将 Sub() 的结果分配给 diff

diff.Sub(powers[i+1], v)

但是这会导致 seqDiffs 的值是 1995(正确的最后一个值)一遍又一遍地重复。我知道这很可能是因为 seqDiffs 只是指向同一内存地址的指针列表,但我不明白为什么以下工作正常

v.Sub(powers[i+1], v)
seqDiffs = append(seqDiffs, v)

这导致 seqDiffs 是从 3 到 1995 的所有奇数的列表,这是正确的,但它本质上不也是指向同一内存地址的指针列表吗? 另外,为什么以下内容也应该导致 seqDiffs 也是指向同一内存地址的指针列表?

diff = v.Sub(powers[i+1], v)
seqDiffs = append(seqDiffs, diff)

我也尝试过以下方式

diff := new(*big.Int)
for i, v := range powers {
if i == len(powers)-2 {
    break
}
diff.Sub(powers[i+1], v)
seqDiffs = append(seqDiffs, diff)
}

但从 ide 收到这些错误:

*./sequentialPowers.go:26: calling method Sub with receiver diff (type **big.Int) requires explicit dereference
./sequentialPowers.go:27: cannot use diff (type **big.Int) as type *big.Int in append*

如何进行“显式”取消引用?

【问题讨论】:

  • 对于您的最后一个问题,您最终得到了一个指向指针的指针。 new() 返回一个指向某个东西的指针,在 new 中你已经把它变成了一个 big.Int 的指针。所以diff := new(*big.Int) 使 diff 成为指向指针的指针。这意味着它不能在您尝试使用它的地方使用,因为它只需要一个指向 big.Int 的指针。

标签: pointers go variable-assignment biginteger


【解决方案1】:

在 Go 中调试指针问题时,了解发生了什么的一种方法是使用 fmt.Printf%p 打印感兴趣变量的内存地址。

关于您的第一个问题,为什么将diff.Sub(powers[i+1], v) 的结果附加到*big.Int 的切片会导致每个索引都是相同值的切片-您正在更新内存地址@987654327 处的值@ 分配给该指针的副本并将其附加到切片。因此,切片中的所有值都是指向相同值的指针。

打印diff 的内存地址将显示这种情况。填充切片后 - 执行以下操作:

for _, val := range seqDiffs {
    fmt.Printf("%p\n", val) // when i ran this - it printed 0xc4200b7d40 every iteration
}

在第二个示例中,值 v 是指向不同地址的 big.Int 的指针。您将v.Sub(..) 的结果分配给diff,这会更新diff 指向的底层地址。因此,当您将 diff 附加到切片时,您将在唯一地址处附加指针的副本。使用fmt.Printf 你可以看到这样 -

var seqDiffs []*big.Int
diff := new(big.Int)
for i, v := range powers {
    if i == len(powers)-2 {
        break
    }
    diff = v.Sub(powers[i+1], v)
    fmt.Printf("%p\n", diff) // 1st iteration 0xc4200109e0, 2nd 0xc420010a00, 3rd 0xc420010a20, etc
    seqDiffs = append(seqDiffs, diff)
} 

关于你的第二个问题 - 在 Go 中使用 new 关键字分配指定类型的内存但不初始化它(check the docs)。在您的情况下,对new 的调用会分配一个指向big.Int (**big.Int) 的指针类型的指针,因此编译器错误提示您不能在对append 的调用中使用此类型。

要显式取消引用 diff 以便在其上调用 Sub,您必须将代码修改为以下内容:

(*diff).Sub(powers[i+1], v)

在 Go 中,选择器表达式会为您解引用指向结构的指针,但在这种情况下,您要在指向指针的指针上调用方法,因此您必须显式地解引用它。

可以在here 找到有关在 Go 中调用结构(选择器表达式)的方法的非常丰富的读物

并将其添加到切片中

seqDiffs = append(seqDiffs, *diff)

【讨论】:

  • 感谢您的回复;这很有帮助。每次使用 math/big 包时都会遇到问题。这将导致很多分钟的类型错误消除,直到我最终获得产生预期输出的代码,但让我感觉,我的编码看起来就像我绕着街区绕了三圈才能到达角落。是否有一种可接受且简单的方法来始终如一地使用这些方法,从而产生具有唯一内存地址的变量?
  • 在循环内部移动 diff 的初始化,diff := new(big.Int) 与 diff.Sub(powers[i+1], v) 配合得很好。我认为这是因为这会在每次迭代的唯一内存地址处创建一个变量 diff?
  • @StephenAdams - 很高兴能提供帮助 - 在循环内移动 diff 是解决问题的好方法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 2010-09-11
相关资源
最近更新 更多