Когда у вас есть фрагмент сложных значений, как вы их обновляете? В этой статье обсуждаются 3 решения. Последний основан на разыменовании указателя, который сочетает в себе лучшее из двух других, но без их недостатков.
Проблема
Давайте возьмем пример простой структуры Person
и предположим, что у нас есть срез из 2 элементов:
type Person struct { Name string Children []Child } persons := []Person{ { Name: "John", Children: []Child{ { Name: "John Jr", }, }, }, { Name: "Jack", }, }
Теперь предположим, что мы хотим изменить Name
первого лица:
p := persons[0] p.Name = "Joe" fmt.Printf("%s", persons[0].Name) // still displays `John`
Хм… 🤨
На самом деле в приведенном выше коде произошло то, что новая переменнаяp
была инициализирована копией содержимого в первой позиции фрагментаpersons
. > Это означает, что изменениеName
вp
не повлияло на содержаниеpersons
.
Решение №1: обновление среза после изменения
Самый простой способ решить проблему — сбросить запись слайса с только что обновленной переменной:
p := persons[0] p.Name = "Joe" persons[0] = p fmt.Printf("%s", persons[0].Name) // displays `Joe`, now
Это работает, но этот дополнительный оператор persons[0] = p
кажется немного нелогичным или неожиданным, не так ли? И становится еще хуже, когда мы пытаемся обновить один из дочерних Name
:
p := persons[0] p.Name = "Joe" c := persons[0].Children[0] c.Name = "Joe Jr" p.Children[0] = c persons[0] = p fmt.Printf("%s", persons[0]) // displays `Joe` and `Joe Jr`
Здесь мы должны дважды скопировать переменную обратно в срез (т. е. один раз на уровень). Это несколько подверженный ошибкам и не очень масштабируемый способ работы.
Решение №2: непосредственное обновление элемента slice
Второе решение заключается в прямом доступе к элементу в слайсе persons
при изменении его поля Name
:
persons
[0].Name = "Jimmy" fmt.Printf("%s",persons
[0].Name) // displays `Jimmy`
Это тоже работает, даже если код немного сложнее читать, если было несколько изменений (но это мое личное мнение).
Однако обратите внимание, что это решение также работает для обновления вложенных элементов:
persons[0].Children[0].Name = "Jimmy Jr"
Решение №3: разыменование указателя на элемент slice
Наличие переменной — это хорошо, но вместо копии элемента slice она может быть указателем на этот элемент. При использовании оператора *
для «разыменования» указателя значение, на которое указывал указатель (т. е. запись в срезе), немедленно обновляется:
pr := &persons[0] (*pr).Name = "Jeff" fmt.Printf("first parent: %s\n", persons[0].Name) // displays `Jeff`
Как и следовало ожидать, это решение работает и для вложенных элементов:
cr := &persons[0].Children[0] (*cr).Name = "Jeff Jr" fmt.Printf("%s\n", persons[0].Children[0]) // displays `Jeff Jr`
Разыменование указателя — это удобное решение, которое дает преимущество наличия переменной, но при этом не нужно иметь дело с последующим «копированием» изменений в резервном фрагменте.
Не стесняйтесь попробовать эти решения самостоятельно на игровой площадке.