这个问题背叛了我对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>
答案 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
。