4.2.2. 遍历切片
一种常见的需求是遍历切片中的所有元素。如果我们想要得到切片中的元素但却不希望修改该元素,我们可以使用for … range循环;如果我们需要修改切片中的元素,我们可以使用带有循环计数器的for循环。对于前者,下面是一个例子。
1 2 3 4 5 6 7 8 9 | amounts := []float64{237.81, 261.87, 273.93, 279.99, 281.07, 303.17, 231.47, 227.33, 209.23, 197.09} sum := 0.0 for _, amount := range amounts { sum += amount } fmt.Printf("Σ %.1f → %.1f\n", amounts, sum) Σ [237.8 261.9 273.9 280.0 281.1 303.2 231.5 227.3 209.2 197.1] → 2503.0 |
for … range循环会初始化一个从0开始的循环计数器,在本例中我们使用了空白标识符(-)丢弃该值,同时从切片中拷贝相应元素的副本。拷贝操作是廉价的(因为它们通过引用传递)。这意味着对元素的任何修改只会影响该副本,而不会影响切片中的元素。
当然,我们可以使用切分操作来遍历切片的一部分。例如,如果我们只想遍历切片的前五个元素,我们可以使用for _,amount:=rangeamounts[:5]语句。
如果我们想要修改切片中的元素,就必须要使用for循环,它作用的是切片的有效索引而不是切片元素的副本。
1 2 3 4 5 6 7 | for i := range amounts { amounts[i] *= 1.05 sum += amounts[i] } fmt.Printf("Σ %.1f → %.1f\n", amounts, sum) Σ [249.7 275.0 287.6 294.0 295.1 318.3 243.0 238.7 219.7 206.9] → 2628.1 |
这里我们将切片中的每个元素增加5%并计算其累加之和。
当然,切片也可以包含自定义类型的元素。下面是一个只有一个自定义方法的自定义类型。
1 2 3 4 5 6 7 8 | type Product struct { name string price float64 } func (product Product) String() string { return fmt.Sprintf("%s (%.2f)", product.name, product.price) } |
上面的代码定义了一个具有一个字符串和一个float64类型字段的结构体Product,同时我们也定义了一个String()方法来使用%v格式符来打印Product中的元素。(之前我们介绍过打印格式符,也简单介绍了自定义类型和方法,在第6章我们将会详细介绍它们。)
1 2 3 4 5 6 7 8 9 10 | products := []*Product{{"Spanner", 3.99}, {"Wrench", 2.49}, {"Screwdriver", 1.99}} fmt.Println(products) for _, product := range products { product.price += 0.50 } fmt.Println(products) [Spanner (3.99) Wrench (2.49) Screwdriver (1.99)] [Spanner (4.49) Wrench (2.99) Screwdriver (2.49)] |
这里我们创建了一个指向Products ([]*Product)的指针切片,并使用三个*Products将其初始化。而之所以可以这样做是因为Go语言足够智能可以意识到[]*Product需要的是指向Products的指针。这里我们使用的是简写形式,products := []*Product{&Product{“Spanner”, 3.99}, &Product{“Wrench”, 2.49}, &Product{“Screwdriver”, 1.99}}。(在4.1节,我们使用了&Type{}语法来创建一个该类型的新值并获取指向它的指针。)
如果我们没有定义Product.String()方法,则%v(在fmt.Println()和一些类似的函数中是被隐式使用的)打印的是Products的内存地址而不是Products本身。此外需要注意的是,Product.String() 方法接受的是Product类型的值而不是* Product,不过这并不成问题,因为Go语言足够智能,它能够自动将*Products解引用,以使我们自定义的方法可以正常工作。
之前我们提到过for … range循环不能用来修改它所遍历的元素,但是这里我们成功的将切片products中的prices都增大了。在每一次遍历中,product变量都被赋值为*Product的副本;它是一个指向切片products中的元素的指针。因此,我们所做的修改是作用于指向的Product值而不是*Product指针的副本。