For loop 在Go中复制循环变量的地址

For loop 在Go中复制循环变量的地址,for-loop,go,reference,For Loop,Go,Reference,在下面的代码示例中,结果不是我所期望的: package main import "fmt" func main() { src := map[int]int{1: 1, 2: 2, 3: 3} fmt.Println("src ", src) dst := make([]*int, 0, len(src)) for k, _ := range src { dst = append(dst, &k) } for _,

在下面的代码示例中,结果不是我所期望的:

package main

import "fmt"

func main() {
    src := map[int]int{1: 1, 2: 2, 3: 3}
    fmt.Println("src ", src)

    dst := make([]*int, 0, len(src))
    for k, _ := range src {
        dst = append(dst, &k)
    }
    for _, a := range dst {
        fmt.Print(*a, " ")
    }
    fmt.Println()
}
结果:

src map[1:1 2:2 3:3]
3 3 3
前往游乐场:

但我明白发生了什么。
k
的不变地址被添加到
dst
,因此当我在
dst
上循环时,每个位置都有相同的值:
3

k
的地址在循环中从未更改,因此第二个循环一直引用该位置,其中包含它拥有的最后一个值
3

如何获取要复制的
k
当前值的地址?我需要这样的东西吗:

for k, _ := range src {
    key = new(int)
    *key = k
    dst = append(dst, key)
}

这似乎很尴尬。

如果您有一个
映射[T]X
,并且您想要得到一个
[]*T
,那么您复制循环变量并获取其地址的方法是正确的

有一种方法比你的方法更简单:

for k := range src {
    key := k
    dst = append(dst, &key)
}
您添加到dst的不是映射中键或值的地址,而是键的副本的地址。这种区别对你来说可能重要,也可能不重要


使用循环变量的地址不起作用的原因是,循环变量是在每次迭代中更新的单个位置。每次将INT和STRUCT之类的值类型分配给新变量时都会被复制。

如果您有一个
映射[T]X
,并且希望获得一个
[]*T
,那么您复制循环变量并获取其地址的方法是正确的

有一种方法比你的方法更简单:

for k := range src {
    key := k
    dst = append(dst, &key)
}
您添加到dst的不是映射中键或值的地址,而是键的副本的地址。这种区别对你来说可能重要,也可能不重要



使用循环变量的地址不起作用的原因是,循环变量是在每次迭代中更新的单个位置。每次将int和struct之类的值类型分配给新变量时,都会复制它。

您在这里想要实现什么?你为什么想要int指针呢?似乎您只是在尝试收集所有映射键?如果您想要该值的副本,则需要复制该值。上一个示例是一种方法,但是为什么要首先尝试获取指向for循环的索引值副本的指针呢?循环变量只分配一次。映射键/值在每次迭代时复制到同一位置。你不应该依赖于它们的地址,因为它们与基础映射中的地址没有联系。这正是我正在尝试做的。我在使用Google云数据存储的GoSDK时发现了这种情况。我需要一个
[]*datastore.Key
来传递给一些函数。我有一个
map[datastore.Key]接口{}
@ralph,是的,这比int更有意义。你想在这里实现什么?你为什么想要int指针呢?似乎您只是在尝试收集所有映射键?如果您想要该值的副本,则需要复制该值。上一个示例是一种方法,但是为什么要首先尝试获取指向for循环的索引值副本的指针呢?循环变量只分配一次。映射键/值在每次迭代时复制到同一位置。你不应该依赖于它们的地址,因为它们与基础映射中的地址没有联系。这正是我正在尝试做的。我在使用Google云数据存储的GoSDK时发现了这种情况。我需要一个
[]*datastore.Key
来传递给一些函数。我有一个
map[datastore.Key]接口{}
@ralph,是的,这比int更有意义。我几乎试过了,但后来我认为编译器只会为
Key
分配一个位置,我也会遇到同样的问题。我想我应该试试。是的,这是围棋中常见的把戏。
key
的范围在循环内,因此它仅限于此迭代。不确定是什么类型的转义分析导致它被分配到堆与堆栈上,但它确实起作用。实际上,看到
k:=k
以相同的名称对变量进行阴影处理也是很常见的。这不是escape分析,词法范围表示新声明对块的其余部分有效。@JimB我的意思是:如果您从未向
dst
添加
key
,编译器可以在每次迭代中重复使用单个堆栈位置。将其保存到切片意味着它不能被覆盖。我对编译器了解不够,不知道它是如何工作的。@captncraig:啊,我明白你的意思了。这是一个可以进行的优化,但这不是你需要知道的任何事情。如果您使用
&i
,它将被转义,因此转义分析会阻止它。下面是一个不安全的示例,可以确保指针不会逃逸:;)(至少在go1.9中是这样)我几乎试过了,但后来我认为编译器只会为
分配一个位置,我也会遇到同样的问题。我想我应该试试。是的,这是围棋中常见的把戏。
key
的范围在循环内,因此它仅限于此迭代。不确定是什么类型的转义分析导致它被分配到堆与堆栈上,但它确实起作用。实际上,看到
k:=k
以相同的名称对变量进行阴影处理也是很常见的。这不是escape分析,词法范围表示新声明对块的其余部分有效。@JimB我的意思是:如果您从未向
dst
添加
key
,编译器可以在每次迭代中重复使用单个堆栈位置。将其保存到切片意味着它不能被覆盖。我对编译器了解不够,不知道它是如何工作的。@captncraig:啊,我明白你的意思了。这是一个可以进行的优化,但这不是你需要知道的任何事情。如果您曾经使用
&i