2017-10-09 11 views
1

Cette question trahit mon manque fondamental de compréhension des pointeurs Golang (ou de n'importe quel pointeur, vraiment), alors s'il vous plaît ours avec moi. J'ai aussi mis un exemple similaire, travaillant sur Go Playground s'il est utile:Réinitialisation des pointeurs dans les boucles

https://play.golang.org/p/Xe-ZRdFWGp

Supposons que j'ai une relation parent/enfant de base avec deux struct:

//Parent 
type User struct{ 
    ID int 
    Rsvps []*Rsvp 
} 

//Child 
type Rsvp struct{ 
    Response string 
} 

À un moment donné , un groupe d'utilisateurs et RSVPs est créé, et les informations stockées dans une base de données. À un moment donné, il sera temps d'extraire des informations de cette base de données et de les réécrire dans ces structures. Lorsque j'utilise une base de données relationnelle, j'essaye généralement de le faire avec une seule requête, dans un modèle que j'utilise depuis de nombreuses années, mais qui n'est peut-être plus le bon. Je vais mettre en place une boucle pour extraire les données. Voici quelques pseudocode avec de nombreux commentaires:

func getUsersAndRsvps() []*User{ 

    sql := "SELECT * FROM users LEFT JOIN rsvps ON users.field1 = rsvps.field1 ORDER BY user.ID;" 

    dataset := getDataset(sql) 

    result = []*User{} 

    rsvps = []*Rsvp{} 
    //Oh, but you already see the problem here, don't you! I'm defining 
    //rsvps outside of the loop, and the values contained at its address 
    //will become values for all users, instead of per user. Yet, how 
    //else can I collect together rsvps while iterating? 

    user = User{} //hold onto a user while iterating 

    lastUserID := int64(0) //track when we move from one user to the next 

    for _, record := range dataset{ 

     thisUserID := record.ID 

     //When this user is different from last user 
     //take the collected rsvps and write them into 
     //the (old) user, then continue iterating... 

     if lastUserID != thisUserID && lastUserID > 0{ 

      //So, right here is the big problem. I'm writing 
      //the address of collected rsvps into the previous user record. 
      //However, on each iteration, that address gets all 
      //new info, such that at the end of the readout, 
      //all users have the same rsvps. 
      user.Rsvps = rsvps 

      result = append(result, &user) 

      //So, yes, I "blank out" the rsvps, but that only goes 
      //to make the last user's rsvps be those shared among all 
      rsvps = []*Rsvp{} 
     } 

     //Gather rsvps 
     rsvp = getRsvp(rsvp) //defined elsewhere 
     rsvps = append(rsvps, &rsvp) 

     user = getUser(record) //defined elsewhere 

     lastUserID := thisUserID 
    } 

    //Capture last record 
    user.Rsvps = rsvps 
    result = append(result, &user) 

} 

Pour rendre la question succincte et nous espérons clairement, comment puis-je itérer à travers un ensemble de données, la collecte des éléments dans une tranche, puis écrire cette tranche en un point unique de mémoire telle que la le prochain jeu d'itérations ne l'écrasera pas?

+0

Chaque variable est écrite dans sa propre mémoire. Si vous voulez qu'une variable persiste en dehors de la portée d'une boucle (ou de n'importe quel bloc), déclarez la variable en dehors de ce bloc. – Adrian

+0

Oui. Je pense que c'est ce que je fais, à moins que je ne comprenne mal votre solution. J'ai placé la variable de pointeur de pointeur en dehors de la portée de boucle, seulement pour la voir réécrire à chaque itération. – Brent

+0

Quelle variable est écrasée? – Adrian

Répondre

1

Le problème ne provient pas d'un pointeur vers Rsvp mais la déclaration suivante (s):

user := User{} //hold onto a user while iterating 

//... omitted for clarity 
for _, record := range dataset{ 
    //... 
    if lastUserID != thisUserID && lastUserID > 0{ 
     //... 

     /*--- The problem is here ---*/ 
     result = append(result, &user) 

     //... 
    } 
    //...  
    user = getUser(record) //defined elsewhere 
    //... 
} 

Au cours de chaque itération, la valeur de la variable user est écrasée, mais puisque la variable user est définie en dehors de la boucle , l'adresse de la variable user (c'est-à-dire &user) restera la même. Par conséquent, les éléments dans la tranche result seront les mêmes, c'est-à-dire adresse à user seule variable, dans lequel sa valeur est capturée à partir du dernier enregistrement. Modifiez l'instruction append à:

//result = append(result, &user) 
u := user 
result = append(result, &u) 

Un exemple minimum pour démontrer le problème peut être trouvé à The Go Playground.

+0

Bon Dieu, quelle réponse "simple". Je vous remercie! Réglez simplement la variable sur une autre variable "fresh" pour la conserver, puis ajoutez-la à la tranche. Brillant! – Brent

+0

En attendant, je venais juste de préparer un MCVE, au cas où quelqu'un aurait un intérêt (maintenant) universitaire: https://play.golang.org/p/yx_SUVXaRe – Brent

0

Que diriez-vous effectuer les opérations suivantes:

package main 

    import (
     "fmt" 
    ) 

    type User struct { 
     ID int 
     Rsvps []*Rsvp 
    } 

    type Rsvp struct { 
     Response string 
    } 

    func main() { 
     users := []int{1, 2, 3} 
     responses := []string{"Yes", "No", "Maybe"} 
     var results []*User 
     for _, i := range users { 
      r := Rsvp{Response: responses[i-1]} // create new variable 
      u := User{ID: i} 
      u.Rsvps = append(u.Rsvps, &r) 
      results = append(results, &u) 
     } 
     for _, r := range results { 
      fmt.Println(r.ID, r.Rsvps[0].Response) 
     } 

    } 

Je pris votre exemple de jeux, dépouillé les commentaires et changé le code pour obtenir votre sortie désirée. Le principal changement est que je ne réutilise pas r. À l'origine, vous étiez toujours en train d'ajouter &r mais en changeant r au début de la boucle. Bien sûr, cela change la mémoire sur laquelle pointe r, ce qui fait que tout est Maybe.

+0

Merci pour votre aide. Dans l'exemple du terrain de jeu, je n'ai pas saisi le problème que j'essaie de rassembler plus d'un RSVP pour un utilisateur. J'ai besoin d'un moyen de définir une tranche avant la boucle, puis rassembler tous les RSVP pour un utilisateur pendant la boucle, puis affecter les RSVP recueillies à l'utilisateur lorsque la boucle frappe un nouvel utilisateur. – Brent