Значение карты Golang изменено после добавления

Может ли кто-нибудь объяснить, почему значение моей структуры «Имя» изменяется после того, как оно было добавлено к новому фрагменту?

package main

import (
    "fmt"
    "strings"

    "golang.org/x/text/language"
)

func main() {
    type Staff struct {
        Name     map[language.Tag]string
        Function string
    }
    var s = []Staff{
        Staff{
            Name:     map[language.Tag]string{language.Japanese: "Mike/Tom"},
            Function: "Cashier",
        },
    }
    checkedStaff := []Staff{}
    // check for multiple staff in one role
    for _, staff := range s {
        if !strings.Contains(staff.Name[language.Japanese], "/") {
            checkedStaff = append(checkedStaff, staff)
            continue
        }
        split := strings.Split(staff.Name[language.Japanese], "/")
        for _, staffNameNative := range split {
            staff.Name[language.Japanese] = strings.TrimSpace(staffNameNative)
            checkedStaff = append(checkedStaff, staff)
            fmt.Printf("%+v", checkedStaff)
        }
    }

}

Я ожидаю, что срез checkedStaff будет содержать следующие значения:
[{Name:map[ja:Mike] Function:Cashier} {Name:map[ja:Tom] Function:Cashier}]

но вместо этого он содержит
[{Name:map[ja:Tom] Function:Cashier} {Name:map[ja:Tom] Function:Cashier}]

поэтому после добавления имя в срезе checkedStaff изменяется.

См. код на Go Playground: https://go.dev/play/p/ovKO4Hc7N3t

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
0
71
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Создайте новый, измененный экземпляр для добавления

package main

import (
    "fmt"
    "strings"

    "golang.org/x/text/language"
)

func main() {
    type Staff struct {
        Name     map[language.Tag]string
        Function string
    }
    var s = []Staff{
        Staff{
            Name:     map[language.Tag]string{language.Japanese: "Mike/Tom"},
            Function: "Cashier",
        },
    }
    checkedStaff := []Staff{}
    // check for multiple staff in one role
    for _, staff := range s {
        if !strings.Contains(staff.Name[language.Japanese], "/") {
            c := Staff{
                Name:     staff.Name,
                Function: staff.Function,
            }
            checkedStaff = append(checkedStaff, c)
            continue
        }
        split := strings.Split(staff.Name[language.Japanese], "/")
        for _, staffNameNative := range split {
            c := Staff{
                Name:     map[language.Tag]string{language.Japanese: strings.TrimSpace(staffNameNative)},
                Function: staff.Function,
            }
            checkedStaff = append(checkedStaff, c)
            fmt.Printf("%+v\n", checkedStaff)
        }
    }

}

Это не совсем правильно. В Go копирование структуры не копирует неявный указатель на ту же базовую структуру; скорее, он фактически выполняет копирование по полям. Проблемой является только карта, поскольку копирование карты копирует неявный указатель на ту же самую базовую карту.

ruakh 21.07.2024 05:09
Ответ принят как подходящий

Более простая демонстрация того же эффекта:

m1 := map[string]int{"key": 1}
m2 := m1
m2["key"] = 2
fmt.Println(m1) // prints "map[key:2]"

[ссылка на игровую площадку]

Причина в том, что когда вы копируете карту, вы на самом деле просто копируете указатель на ту же карту, а не создаете отдельную карту.

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

Чтобы скопировать карту, вы можете использовать maps.Clone [ссылка на документ]:

copyOfStaff := staff
copyOfStaff.Name = maps.Clone(staff.Name)
copyOfStaff.Name[language.Japanese] = strings.TrimSpace(staffNameNative)
checkedStaff = append(checkedStaff, copyOfStaff)

[ссылка на игровую площадку]

Хорошо, давайте немного упростим вашу программу (Go Playground):

package main

import (
    "fmt"

    "golang.org/x/text/language"
)

func main() {
    type Staff struct {
        Name     map[language.Tag]string
        Function string
    }

    staff := Staff{
        Name:     map[language.Tag]string{},
        Function: "Cashier",
    }
    checkedStaff := []Staff{}
    split := []string{"Mike", "Tom"}
    for _, staffNameNative := range split {
        staff.Name[language.Japanese] = staffNameNative
        fmt.Printf(">> checkedStaff + staff: %+v + %+v\n", checkedStaff, staff)
        checkedStaff = append(checkedStaff, staff)
        fmt.Printf("checkedStaff: %+v\n", checkedStaff)
    }
}

Итак, что происходит?

В первом выполнении вы установили staff.Name[language.Japanese] = "Mike", поэтому staff = {Name:map[ja:Mike] Function:Cashier} добавьте его к [], в результате чего получится строка, начинающаяся с [{Name:map[ja:Mike] Function:Cashier}].

Теперь выполняется второй цикл staff.Name[language.Japanese] = "Tom". Поскольку карта является ссылочным типом, вы изменяете значение в checkedStaff, которое теперь равно [{Name:map[ja:Tom] Function:Cashier}], и добавляете его к checkedStaff, поэтому строка продолжается с [{Name:map[ja:Tom] Function:Cashier} {Name:map[ja:Tom] Function:Cashier}].

Обычно вы не хотите изменять исходную структуру, над которой работаете. Если вы что-то трансформируете (путем разделения или обрезки), не возвращайте это в исходную структуру, а создайте новую.

Другие вопросы по теме