Изменить значения во время итерации
Предположим, у меня есть эти типы:
type Attribute struct {
Key, Val string
}
type Node struct {
Attr []Attribute
}
и что я хочу перебрать атрибуты моего узла, чтобы изменить их.
Я хотел бы иметь возможность сделать:
for _, attr := range n.Attr {
if attr.Key == "href" {
attr.Val = "something"
}
}
но так как attr
не указатель, это не сработает, и я должен сделать:
for i, attr := range n.Attr {
if attr.Key == "href" {
n.Attr[i].Val = "something"
}
}
Есть более простой или быстрый способ? Можно ли напрямую получить указатели из range
?
Очевидно, что я не хочу менять структуры только для итерации, и более подробные решения не являются решениями.
Ответы
Ответ 1
Нет, аббревиатура, которую вы хотите, не представляется возможным.
Причиной этого является то, что range
копирует значения из фрагмента, который вы повторяете.
Спецификация о диапазоне говорит:
Range expression 1st value 2nd value (if 2nd variable is present)
array or slice a [n]E, *[n]E, or []E index i int a[i] E
Таким образом, диапазон использует a[i]
как свое второе значение для массивов/срезов, что фактически означает, что
значение копируется, что делает исходное значение неприкосновенным.
Это поведение демонстрируется следующим кодом:
x := make([]int, 3)
x[0], x[1], x[2] = 1, 2, 3
for i, val := range x {
println(&x[i], "vs.", &val)
}
Код печатает полностью разные ячейки памяти для значения из диапазона и фактического
значение в срезе:
0xf84000f010 vs. 0x7f095ed0bf68
0xf84000f014 vs. 0x7f095ed0bf68
0xf84000f018 vs. 0x7f095ed0bf68
Итак, единственное, что вы можете сделать, это либо использовать указатели, либо индекс, как уже было предложено jnml и peterSO.
Ответ 2
Кажется, вы просите что-то эквивалентное этому:
package main
import "fmt"
type Attribute struct {
Key, Val string
}
type Node struct {
Attr []Attribute
}
func main() {
n := Node{
[]Attribute{
{"key", "value"},
{"href", "http://www.google.com"},
},
}
fmt.Println(n)
for i := 0; i < len(n.Attr); i++ {
attr := &n.Attr[i]
if attr.Key == "href" {
attr.Val = "something"
}
}
fmt.Println(n)
}
Вывод:
{[{key value} {href http://www.google.com}]}
{[{key value} {href something}]}
Это позволяет избежать создания, возможно, большой копии значений типа Attribute
, за счет проверки границ срезов. В вашем примере тип Attribute
относительно невелик, две ссылки на срез string
: 2 * 3 * 8 = 48 байт на 64-битной архитектуре.
Вы также можете просто написать:
for i := 0; i < len(n.Attr); i++ {
if n.Attr[i].Key == "href" {
n.Attr[i].Val = "something"
}
}
Но способ получения эквивалентного результата с предложением range
, который создает копию, но минимизирует проверки границ срезов, заключается в следующем:
for i, attr := range n.Attr {
if attr.Key == "href" {
n.Attr[i].Val = "something"
}
}
Ответ 3
Например:
package main
import "fmt"
type Attribute struct {
Key, Val string
}
type Node struct {
Attr []*Attribute
}
func main() {
n := Node{[]*Attribute{
&Attribute{"foo", ""},
&Attribute{"href", ""},
&Attribute{"bar", ""},
}}
for _, attr := range n.Attr {
if attr.Key == "href" {
attr.Val = "something"
}
}
for _, v := range n.Attr {
fmt.Printf("%#v\n", *v)
}
}
Игровая площадка
Выход
main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
Альтернативный подход:
package main
import "fmt"
type Attribute struct {
Key, Val string
}
type Node struct {
Attr []Attribute
}
func main() {
n := Node{[]Attribute{
{"foo", ""},
{"href", ""},
{"bar", ""},
}}
for i := range n.Attr {
attr := &n.Attr[i]
if attr.Key == "href" {
attr.Val = "something"
}
}
for _, v := range n.Attr {
fmt.Printf("%#v\n", v)
}
}
Игровая площадка
Вывод:
main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
Ответ 4
Я бы адаптировал ваше последнее предложение и использовал версию диапазона только для индекса.
for i := range n.Attr {
if n.Attr[i].Key == "href" {
n.Attr[i].Val = "something"
}
}
Мне кажется более простым ссылаться на n.Attr[i]
явно в обеих строках, которые проверяют Key
и строку, которая устанавливает Val
, вместо использования attr
для одного и n.Attr[i]
для другого.