Golang中如何向切片的切片追加元素

Golang中如何向切片的切片追加元素 我目前正在编写第二个Go程序——第一个是"Hello World"的修改版,叫做"Hello Pete"——我遇到了切片的一个特性,对此我感到困惑。希望这里有人能好心帮忙解释一下。

我想要一次读取一个CSV文件的记录,提取字段的子集,并用它们来填充一个字符串切片的切片。我的问题在于这个过程的最后部分。

我尝试在这里和Playground中重现我所看到的行为。

package main

import (
	"fmt"
)

func main() {
	mySOS := make([][]string, 1)
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n\n", mySOS, mySOS)
	
	row0 := []string{"Twas", "brillig", "and", "the", "slithy", "toves"}
	row1 := []string{"did", "gyre", "and", "gimble", "in", "the"}
	row2 := []string{"wabe", "all", "mimsey", "were", "the", "borogoves"}
	// row3 := []string{"and", "the", "mome", "raths", "outgrabe", "."}

	s := make([]string, 3)
	
	s[0] = row0[2]
	s[1] = row0[3]
	s[2] = row0[4]
	
	fmt.Printf("s is of type %T and has value %v\n", s, s)
	mySOS = append(mySOS, s)
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n\n", mySOS, mySOS)
	
	s[0] = row1[2]
	s[1] = row1[3]
	s[2] = row1[4]
	
	fmt.Printf("s is of type %T and has value %v\n", s, s)
	mySOS = append(mySOS, s)
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n\n", mySOS, mySOS)
	
	s[0] = row2[2]
	s[1] = row2[3]
	s[2] = row2[4]
	
	fmt.Printf("s is of type %T and has value %v\n", s, s)
	mySOS = append(mySOS, s)
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n", mySOS, mySOS)
}

https://play.golang.org/p/o0ufVU3sBzR

我的困惑在于,mySOS最终总是完全填充了测试数据中的最后一个切片。在花了相当长的时间盯着它看之后,我尝试了一种稍微不同的方法,可以在这里看到。

package main

import (
	"fmt"
)

func main() {
	mySOS := make([][]string, 1)
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n\n", mySOS, mySOS)
	
	row0 := []string{"Twas", "brillig", "and", "the", "slithy", "toves"}
	row1 := []string{"did", "gyre", "and", "gimble", "in", "the"}
	row2 := []string{"wabe", "all", "mimsey", "were", "the", "borogoves"}
	// row3 := []string{"and", "the", "mome", "raths", "outgrabe", "."}
	
	/*
	s := make([]string, 3)
	
	s[0] = row0[2]
	s[1] = row0[3]
	s[2] = row0[4]
	*/
	
	// fmt.Printf("s is of type %T and has value %v\n", s, s)
	mySOS = append(mySOS, row1[2:5])
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n\n", mySOS, mySOS)
	
	/*
	s[0] = row1[2]
	s[1] = row1[3]
	s[2] = row1[4]
	*/
	
	// fmt.Printf("s is of type %T and has value %v\n", s, s)
	mySOS = append(mySOS, row0[2:5])
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n\n", mySOS, mySOS)
	
	/*
	s[0] = row2[2]
	s[1] = row2[3]
	s[2] = row2[4]
	*/
	
	// fmt.Printf("s is of type %T and has value %v\n", s, s)
	mySOS = append(mySOS, row2[2:5])
	fmt.Printf("mySliceOfSlices is of type %T and has value %v\n", mySOS, mySOS)
}

https://play.golang.org/p/nbWWEjHTzEX

这次mySOS包含了我期望的数据。但我仍然不明白为什么初始代码的结果会不同。

如前所述,如果有人能帮助澄清我明显的误解,我将不胜感激。


更多关于Golang中如何向切片的切片追加元素的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

啊,好的。那么我是否可以这样理解:它引用的是s(沿用第一个例子)——这个引用被附加到mySOS上,而不是值本身?

更多关于Golang中如何向切片的切片追加元素的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


明白了。非常感谢您的帮助,以及那篇写得很好且清晰易懂的文章链接。

切片"值"包含指向"底层数组"的指针。(参见《切片内部机制》https://blog.golang.org/go-slices-usage-and-internals

因此,没错,您正在通过切片引用不断更新底层数组,并持续向其追加引用。

在你的第一个示例中,你不断重新定义切片 s[:3],导致它最终指向最后一组值。每个"s"切片都需要一个新的底层数组。请参阅:https://play.golang.org/p/Vz1b8afT_eJ

你遇到的问题是由于切片底层数组的共享导致的。在你的第一个代码示例中,你重复使用同一个切片 s 来追加到 mySOS 中,这导致所有追加的切片都指向同一个底层数组。

当你在第一个示例中这样做时:

s := make([]string, 3)

s[0] = row0[2]
s[1] = row0[3]
s[2] = row0[4]
mySOS = append(mySOS, s)

s[0] = row1[2]  // 这会修改底层数组
s[1] = row1[3]
s[2] = row1[4]
mySOS = append(mySOS, s)  // 再次追加同一个切片

每次修改 s 时,你实际上是在修改同一个底层数组,而 mySOS 中存储的是对这个切片的引用。因此,所有看起来不同的切片实际上都指向相同的内存位置。

正确的做法是每次创建一个新的切片:

package main

import (
	"fmt"
)

func main() {
	mySOS := make([][]string, 0)  // 从空切片开始
	
	row0 := []string{"Twas", "brillig", "and", "the", "slithy", "toves"}
	row1 := []string{"did", "gyre", "and", "gimble", "in", "the"}
	row2 := []string{"wabe", "all", "mimsey", "were", "the", "borogoves"}

	// 方法1:每次创建新切片
	s1 := []string{row0[2], row0[3], row0[4]}
	mySOS = append(mySOS, s1)
	
	s2 := []string{row1[2], row1[3], row1[4]}
	mySOS = append(mySOS, s2)
	
	s3 := []string{row2[2], row2[3], row2[4]}
	mySOS = append(mySOS, s3)
	
	fmt.Printf("mySliceOfSlices: %v\n", mySOS)
}

或者使用切片表达式(如你的第二个示例),这会创建新的切片:

package main

import (
	"fmt"
)

func main() {
	mySOS := make([][]string, 0)
	
	row0 := []string{"Twas", "brillig", "and", "the", "slithy", "toves"}
	row1 := []string{"did", "gyre", "and", "gimble", "in", "the"}
	row2 := []string{"wabe", "all", "mimsey", "were", "the", "borogoves"}

	mySOS = append(mySOS, row0[2:5])
	mySOS = append(mySOS, row1[2:5])
	mySOS = append(mySOS, row2[2:5])
	
	fmt.Printf("mySliceOfSlices: %v\n", mySOS)
}

对于你的CSV处理场景,推荐的做法是:

package main

import (
	"encoding/csv"
	"fmt"
	"strings"
)

func main() {
	csvData := `Twas,brillig,and,the,slithy,toves
did,gyre,and,gimble,in,the
wabe,all,mimsey,were,the,borogoves`

	reader := csv.NewReader(strings.NewReader(csvData))
	records, _ := reader.ReadAll()
	
	mySOS := make([][]string, 0)
	
	for _, record := range records {
		if len(record) >= 5 {
			// 提取字段2-4(索引2,3,4)
			subset := make([]string, 3)
			copy(subset, record[2:5])
			mySOS = append(mySOS, subset)
		}
	}
	
	fmt.Printf("Processed data: %v\n", mySOS)
}

关键点在于理解切片是对底层数组的视图,重复使用同一个切片变量会导致所有引用都指向相同的数据。每次需要新的独立数据时,应该创建新的切片。

回到顶部