Вопрос о карте, строковых указателях и закрытии - PullRequest
1 голос
/ 20 июня 2020

Я обнаружил ошибку в своем коде, когда пытался преобразовать map[string]string в map[string]*string (преобразование требуется API, который я использовал).

Проблема

В моем первом методе я попытался перебрать каждую запись в исходной карте aMap и преобразовать строковое значение каждой записи в строковый указатель и присвоить это значение указателя целевой карте, bMap используя тот же ключ, что и aMap. Изначально я ожидал, что разыменованное значение, содержащееся в bMap, будет таким же, как aMap под тем же ключом. Однако оказалось, что указанное значение bMap не имеет того же значения, что и aMap под тем же ключом.

Мои вопросы:

  1. Почему возникает эта проблема?
  2. Мне удалось решить проблему, используя второй метод, сначала определив функцию, которая получает строку и возвращает указатель, таким образом, я смог правильно вернуть значение, для которого установлено значение, чтобы использовать то же значение, что и aMap, под тем же ключом. Почему этот метод работает?
  3. Также любопытно, почему &(*v) синтаксическая ошибка в Go? Я также попытался объявить фиктивную переменную val := *v и назначить ее bMap с помощью bMap[key] = &val, но безрезультатно. В сообщении об ошибке указано: invalid indirect of v (type string)

Исходный код

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

func main() {
    aMap := make(map[string]string)
    aMap["foo"] = "bar"
    aMap["bar"] = "baz"
    aMap["baz"] = "foo"

    bMap := make(map[string]*string)
    for k, v := range(aMap){
        bMap[k] = &v
    }

    // first method
    fmt.Println("1st method, map[string]*string, bMap") 
    for k, v := range(bMap){
        fmt.Printf("bMap[%s] = %s / %s, aMap[%s] = %s\n",
        k, *bMap[k], *v, k, aMap[k])
    }

    pString := func(v string) *string{ return &v }
    for k, v := range(aMap){
        bMap[k] = pString(v)
    }

    // second method
    fmt.Println("2nd method, map[string]*string, bMap")
    for k, v := range(bMap){
        fmt.Printf("bMap[%s] = %s / %s, aMap[%s] = %s\n",
        k, *bMap[k], *v, k, aMap[k])
    }

    // expected results
    fmt.Println("Expected result: map[string]string, cMap")
    cMap := make(map[string]string)
    for k, v := range(aMap){
        cMap[k] = v
    }
    for k, v := range(cMap){
        fmt.Printf("cMap[%s] = %s / %s, aMap[%s] = %s\n",
        k, cMap[k], v, k, aMap[k])
    }
}

Выход

1st method, map[string]*string, bMap
bMap[baz] = foo / foo, aMap[baz] = foo
bMap[foo] = foo / foo, aMap[foo] = bar
bMap[bar] = foo / foo, aMap[bar] = baz
2nd method, map[string]*string, bMap
bMap[baz] = foo / foo, aMap[baz] = foo
bMap[foo] = bar / bar, aMap[foo] = bar
bMap[bar] = baz / baz, aMap[bar] = baz
Expected result: map[string]string, cMap
cMap[baz] = foo / foo, aMap[baz] = foo
cMap[foo] = bar / bar, aMap[foo] = bar
cMap[bar] = baz / baz, aMap[bar] = baz

Большое спасибо.

Ответы [ 2 ]

3 голосов
/ 20 июня 2020

Первый метод в вопросе использует адрес одной переменной l oop v для всех ключей. Значение, которое вы видите, является последним значением, установленным на v.

Исправьте, объявив новую переменную для каждой итерации и взяв адрес этой переменной.

bMap := make(map[string]*string)
for k, v := range aMap {
    v := v // declare new variable v initialized with value from outer v
    bMap[k] = &v
}

Второй метод в вопросе также объявляет новую переменную для каждой итерации в l oop. Новая переменная - это аргумент функции.

Этот ответ демонстрирует идиоматический c подход к решению проблемы. См. Go FAQ: Что происходит с закрытием, работающим как горутины? для обсуждения той же проблемы в контексте закрытий и горутин.

2 голосов
/ 20 июня 2020

Проблема в методе 1 заключается в том, что вы берете не адрес строки, а адрес переменной, в которой она содержится. В Go, &v возвращает адрес переменной v. Когда у вас есть al oop, например:

for k, v := range aMap {
    ...
}

Переменные k и v, которые вы объявляете в начале l oop, являются теми же переменными, которые используются на протяжении l oop. Им просто присваиваются разные значения на каждой итерации. Внутри этого l oop &v всегда принимает одно и то же значение: адрес v. Вот почему все записи вашей карты приходят к "foo": "foo" - это последнее значение, помещенное в v, и оно все еще там.

Вы можете увидеть это поведение в действии, изменив строковое значение . Все ключи карты изменятся:

*bMap["foo"] = "quux"
fmt.Println(*bMap["bar"]) // prints "quux"

Метод 2 работает, потому что каждый вызов функции имеет свои собственные локальные переменные. Go гарантирует, что если вы вернете адрес локальной переменной из функции, эта переменная будет размещена в куче, где ее можно будет использовать столько, сколько потребуется. Итак, ваша вспомогательная функция сообщает Go выделить новую string переменную, скопировать в нее переданную строку, а затем вернуть ее адрес.

Вот еще один метод, который будет работать:

dMap := make(map[string]*string)
for k, v := range(aMap){
    dMap[k] = new(string)
    *dMap[k] = v
}

Это выделяет новую строковую переменную, сохраняет ее адрес в карте, а затем копирует в нее v. Если вы собираетесь делать это часто, вероятно, лучше всего подойдет вспомогательная функция.

Код, который вы пробовали, например:

v := "foo"
val := *v
bMap[key] = &val

не работает, потому что вы говорите: " v - строка; теперь сохраните значение, на которое указывает строка, в val ", но строка не является указателем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...