2017-10-09 32 views
1

这个问题背离了我对Golang指针(或者任何指针)的基本理解,所以请耐心等待。我也提出了类似的,工作例如在去游乐场,如果它是有帮助的:重置循环中的指针

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

假设我有两个结构基本父/子关系:

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

//Child 
type Rsvp struct{ 
    Response string 
} 

在一些点,创建了一堆用户和RSVP,并将信息存储在数据库中。在某个时候,它将来到从该数据库提取信息并将其写回到这些结构中的时间。在使用关系数据库时,我通常会尝试使用单个查询来做到这一点,这种模式我已经使用了很多年了,但这可能不再是正确的方式。我将设置一个循环来提取数据。下面是一些伪代码有许多评论:

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) 

} 

为了使问题简洁,并希望清楚,我怎么通过数据集进行迭代,收集项目成片,然后写切片成一个独特的记忆点,使得下一组迭代不会覆盖它?

+0

每个变量都写入自己的内存中。如果你想让一个变量在一个循环的作用域(或任何块)之外持久化,则在该块之外声明该变量。 – Adrian

+0

是的。我认为这就是我所做的,除非我误解了你的解决方案。我将指针切片变量设置在循环范围之外,只是为了观察它在每次迭代时被重写。 – Brent

+0

哪个变量被覆盖? – Adrian

回答

1

问题并不造成一个指向Rsvp但下面的语句(多个):

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 
    //... 
} 

在每次迭代期间,变量user的值被重写,但由于可变user在循环之外定义,地址到变量user(即&user)将保持不变。结果,result切片中的元素将是相同的,即地址到单个user变量,其中它的值是从最后记录中捕获的。更改append声明:

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

最低例子来说明这个问题可以在The Go Playground找到。

+0

天哪,什么是“简单”的答案。谢谢!只需将变量设置为等于另一个“新鲜”变量即可保留该变量,然后将该变量追加到切片。辉煌! – Brent

+0

同时,我刚刚开始准备MCVE,以防任何人有(现在)学术兴趣:https://play.golang.org/p/yx_SUVXaRe – Brent

0

你怎么样执行以下操作:

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) 
     } 

    } 

我已经采取了你的游乐场例如,剥离的意见,改变了代码才能获得所需的输出。主要变化是我不重复使用r。最初你总是追加&r,但在循环开始时更改r。当然这会改变r指向的内存,使得所有内容都变成Maybe

+0

感谢您的支持。在游乐场的例子中,我没有捕捉到我试图为用户收集多个RSVP的问题。我需要一些方法在循环之前定义一个切片,然后在循环过程中为用户收集所有RSVP,然后在循环触及新用户时将收集的RSVP分配给用户。 – Brent