重置循环中的指针

时间:2017-10-09 19:42:57

标签: pointers go

这个问题背叛了我对Golang指针(或任何指针,真的)的基本缺乏理解,所以请耐心等待。如果它有用,我也会在Go Playground上放一个类似的工作示例:

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)

}

为了使问题简明扼要,希望清楚,如何遍历数据集,将项目收集到切片中,然后将该切片写入唯一的内存点,以便下一组迭代不会覆盖它? / p>

2 个答案:

答案 0 :(得分: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找到证明问题的最低示例。

答案 1 :(得分: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