Golang,追加只保留最后一个元素。

huangapple go评论86阅读模式
英文:

Golang, appending leaves only last element

问题

这是示例代码:

package main

import (
	"fmt"
)

type Product struct {
	Id       int64
	Title    string
	AttrVals []string
}

type ProductAttrValView struct {
	Product
	Attr string
}

type ProductAttrVal struct {
	Attr    string
	Product int64
	Value   string
}

func main() {
	p := Product{Id: 1, Title: "test", AttrVals: []string{}}
	var prod *Product
	prodViews := []ProductAttrValView{
		ProductAttrValView{Product: p, Attr: "text1"},
		ProductAttrValView{Product: p, Attr: "text2"},
		ProductAttrValView{Product: p, Attr: "text3"},
		ProductAttrValView{Product: p, Attr: "text4"},
	}

	// 将 View 中的数据合并到 Product 中的 Attrs 中
	for _, pview := range prodViews {
		if prod == nil {
			prod = &pview.Product
			prod.AttrVals = make([]string, 0, len(prodViews))
		}
		if pview.Attr != "" {
			fmt.Printf("appending '%s' to %p\n", pview.Attr, prod) // 调试输出
			prod.AttrVals = append(prod.AttrVals, pview.Attr)
		}
	}
	fmt.Printf("%+v\n", prod) // 调试输出

}

这段代码从数据库中返回了一些数据,存储在 ProductAttrValView 结构体中,然后将其放入 Product 结构体中,并填充 Product.AttrVals

输出结果为:

&{Id:1 Title:test AttrVals:[text4]}

而期望的结果是:

&{Id:1 Title:test AttrVals:[text1 text2 text3 text4]}

所以,所有的文本应该被追加到 Attrs 切片中,但由于某种原因,只有最后一个元素保留在 Attrs 切片中。

英文:

Here is example code:

package main

import (
	"fmt"
)

type Product struct {
	Id       int64
	Title    string
	AttrVals []string
}

type ProductAttrValView struct {
	Product
	Attr string
}

type ProductAttrVal struct {
	Attr    string
	Product int64
	Value   string
}

func main() {
	p := Product{Id: 1, Title: "test", AttrVals: []string{}}
	var prod *Product
	prodViews := []ProductAttrValView{
		ProductAttrValView{ Product: p, Attr: "text1" },
		ProductAttrValView{ Product: p, Attr: "text2" },
		ProductAttrValView{ Product: p, Attr: "text3" },
		ProductAttrValView{ Product: p, Attr: "text4" },
	}

	// collapse join View to Product with Attrs
	for _, pview := range prodViews {
		if prod == nil {
			prod = &pview.Product
			prod.AttrVals = make([]string, 0, len(prodViews))
		}
		if pview.Attr != "" {
			fmt.Printf("appending '%s' to %p\n", pview.Attr, prod) // output for debug
			prod.AttrVals = append(prod.AttrVals, pview.Attr)
		}
	}
	fmt.Printf("%+v\n", prod) // output for debug

}

http://play.golang.org/p/949w5tYjcH

Here i have some returned data from DB in ProductAttrValView struct and want
put it into Product struct and also fill Product.AttrVals

It prints:

> &{Id:1 Title:test AttrVals:[text4]}

While i expect this:

> &{Id:1 Title:test AttrVals:[text1 text2 text3 text4]}

So, all text should be appended, but for some reason only the last element stays in Attrs slice.

答案1

得分: 1

你在for-range循环中重复使用变量,并且每次迭代都修改了该变量的值。你可以使用以下技巧在每次迭代中创建一个新值:

pview := pview

http://play.golang.org/p/qtJXxdtuq2

你还使用长度为4的切片进行初始化,但是你使用append添加了另一个值(忽略了前4个)。你可能想设置切片的容量而不是长度:

prod.AttrVals = make([]string, 0, len(prodViews))

由于prod在每次迭代中都会改变,如果你明确初始化prod的值,而不是分配&pview.Product的地址,代码会更清晰易懂:

prod = &Product{AttrVals: make([]string, 0, len(prodViews))}

[时间线]

  1. 你创建了一个包含初始化的[]string的单个产品p
  2. 同一个p被分配给我们将要迭代的所有prodViews
  3. 在第一次循环迭代中,你将*prod赋值为初始的p值,然后将AttrVals更改为一个新的长度为4的[]string。这不会改变原始的p
  4. pview.Attr被追加到prod.AttrVals,使其长度为5,并创建一个新的底层数组。这不会反映在p的值中。
  5. 在后续的迭代中,pview被下一个prodViews中的值覆盖。这也会覆盖prod的值,因为它指向pview.Product的值。这意味着prod.AttrVals现在与原始的p相同。
  6. pview.Attr被追加到长度为0的切片中,并且底层数组被替换为更大的容量,所以pview.Attr仍然不包含在原始的p中。
  7. pview再次被下一个值覆盖,该值包含原始的p值,将你的AttrVals长度重置为0,并创建一个空数组。
  8. 这个循环一直持续到打印最后一个单个值。
英文:

You are re-using variables in your for-range loop, and each iteration modifies the value of that same variable. You can create a new value each iteration with the trick:

pview := pview

http://play.golang.org/p/qtJXxdtuq2

You also initialize the slice with a length of 4, but you append another value (ignoring the first 4). You likely meant to set the capacity of the slice as opposed to the length:

prod.AttrVals = make([]string, 0, len(prodViews))

Because the value of prod is changing each iteration, the code would be a lot less confusing if you specifically initialized the prod value, instead of assigning the address of &pview.Product

prod = &Product{AttrVals: make([]string, 0, len(prodViews))}

[time line]

  1. You create a single product p, containing an initialized []string
  2. That same p is assigned to all prodViews that we will iterate over.
  3. On the first iteration through the loop, you assign *prod to that initial p value, then change AttrVals to a new []string of length 4. This doesn't alter the original p.
  4. pview.Attr is appended to prod.AttrVals, making a it length 5, and creating a new underlying array. This isn't reflected in the values of p.
  5. On subsequent iterations, pview is overwritten with the next value in prodViews. This overwrites the value of prod too, since it points to the pview.Product value. This means that prod.AttrVals is now the same one from the original p.
  6. pview.Attr is appended to a slice of length 0, and the underlying array is replaced with more capacity, so pview.Attr still isn't contained in the original p.
  7. pview is again overwritten with the next value, which contains original p values, setting your AttrVals length back to 0 with an empty array.
  8. The cycle continues until the final single value is printed.

huangapple
  • 本文由 发表于 2014年12月23日 04:36:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/27610039.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定