Supposons que j'ai ces types:
type Attribute struct {
Key, Val string
}
type Node struct {
Attr []Attribute
}
et que je veux itérer sur les attributs de mon noeud pour les changer.
J'aurais aimé pouvoir faire:
for _, attr := range n.Attr {
if attr.Key == "href" {
attr.Val = "something"
}
}
mais comme attr
n'est pas un pointeur, cela ne fonctionnerait pas et je dois le faire:
for i, attr := range n.Attr {
if attr.Key == "href" {
n.Attr[i].Val = "something"
}
}
Y a-t-il un moyen plus simple ou plus rapide? Est-il possible d'obtenir directement des pointeurs de range
?
Évidemment, je ne veux pas changer les structures juste pour l'itération et des solutions plus verbeuses ne sont pas des solutions.
Non, l'abréviation que vous voulez n'est pas possible.
La raison en est que range
copie les valeurs de la tranche sur laquelle vous effectuez une itération. Le spécification sur la plage dit:
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
La plage utilise donc a[i]
en tant que deuxième valeur pour les tableaux/tranches, ce qui signifie en fait que la valeur est copiée, ce qui rend la valeur d'origine intouchable.
Ce comportement est démontré par le code suivant :
x := make([]int, 3)
x[0], x[1], x[2] = 1, 2, 3
for i, val := range x {
println(&x[i], "vs.", &val)
}
Le code vous imprime des emplacements de mémoire complètement différents pour la valeur de la plage et la valeur réelle dans la tranche:
0xf84000f010 vs. 0x7f095ed0bf68
0xf84000f014 vs. 0x7f095ed0bf68
0xf84000f018 vs. 0x7f095ed0bf68
Donc, la seule chose que vous pouvez faire est d’utiliser des pointeurs ou l’index, comme déjà proposé par jnml et peterSO.
Vous semblez demander quelque chose d'équivalent à ceci:
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)
}
Sortie:
{[{key value} {href http://www.google.com}]}
{[{key value} {href something}]}
Cela évite de créer une copie - éventuellement volumineuse - de type Attribute
de valeur, aux dépens des contrôles de limite de tranche. Dans votre exemple, taper Attribute
est relativement petit, deux références de slices string
: 2 * 3 * 8 = 48 octets sur une machine d'architecture 64 bits.
Vous pouvez aussi simplement écrire:
for i := 0; i < len(n.Attr); i++ {
if n.Attr[i].Key == "href" {
n.Attr[i].Val = "something"
}
}
Cependant, la manière d'obtenir un résultat équivalent avec une clause range
, qui crée une copie mais minimise les contrôles de limite de tranche, est la suivante:
for i, attr := range n.Attr {
if attr.Key == "href" {
n.Attr[i].Val = "something"
}
}
J'adapterais votre dernière suggestion et utiliserais la version d'index uniquement de range.
for i := range n.Attr {
if n.Attr[i].Key == "href" {
n.Attr[i].Val = "something"
}
}
Il me semble plus simple de parler de n.Attr[i]
explicitement dans la ligne qui teste Key
et la ligne qui définit Val
, plutôt que d'utiliser attr
pour one et n.Attr[i]
pour l'autre.
Par exemple:
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)
}
}
Sortie
main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
Approche alternative:
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)
}
}
Sortie:
main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}