Go语言学习查缺补漏ing Day10

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」。

Go语言学习查缺补漏ing Day10

本文收录于我的专栏:《让我们一起Golang》

零、前言

因为笔者基础不牢,在使用Go语言的时候经常遇到很多摸不着头脑的问题,所以笔者下定决心好好对Go语言进行查漏补缺,本【Go语言查缺补漏ing】系列主要是帮助新手Gopher更好的了解Go语言的易错点、重难点。希望各位看官能够喜欢,点点赞、关注一下呗!

一、for range 使用:=的形式迭代变量时出现的一个问题

看下面这段代码,思考输出结果是什么?为什么?

package main
​
import (
    "fmt"
)
​
type Foo struct {
    bar string
}
​
func main() {
    slice1 := []Foo{
        {"A"},
        {"B"},
        {"C"},
    }
    slice2 := make([]*Foo, len(slice1))
    for index, value := range slice1 {
        slice2[index] = &value
    }
    fmt.Println(slice1[0], slice1[1], slice1[2])
    fmt.Println(slice2[0], slice2[1], slice2[2])
}
​
复制代码

我们想要获得的结果应该是:

{A} {B} {C}
&{A} &{B} &{C}
复制代码

但是,实际上是这样的吗?来看一下输出结果:

{A} {B} {C}
&{C} &{C} &{C}
复制代码

为什么会出现这种情况?

因为当我们在for range循环中使用:=的形式迭代变量时,索引index以及值value会在每次循环都会被重用,而不是新开辟一块新的内存空间存放。所以这里slice2每一次放入的都是值value的地址,所以最后一次循环时,value是{C},而因为前面存入的地址都是value的地址,故存入的都是{C}。

有什么解决办法?

第一个解决办法自然是不存储指针,而是存值:

package main
​
import (
    "fmt"
)
​
type Foo struct {
    bar string
}
​
func main() {
    slice1 := []Foo{
        {"A"},
        {"B"},
        {"C"},
    }
    slice2 := make([]Foo, len(slice1))
    for index, value := range slice1 {
        slice2[index] = value
    }
    fmt.Println(slice1[0], slice1[1], slice1[2])
    fmt.Println(slice2[0], slice2[1], slice2[2])
}
复制代码
{A} {B} {C}
{A} {B} {C}
复制代码

这样虽然还是重用,但是每次value的值都被保存了,保存的不是指针,因此最后输出结果没问题。但是这个貌似输出结果不是我们想要的那样。

那么还有没有其它办法呢?

package main
​
import (
    "fmt"
)
​
type Foo struct {
    bar string
}
​
func main() {
    slice1 := []Foo{
        {"A"},
        {"B"},
        {"C"},
    }
    slice2 := make([]*Foo, len(slice1))
    for index := range slice1 {
        slice2[index] = &slice1[index]
    }
    fmt.Println(slice1[0], slice1[1], slice1[2])
    fmt.Println(slice2[0], slice2[1], slice2[2])
}
​
复制代码

我们可以这样,虽然value的地址是固定的,但是slice1[index]的地址是不一样的。我们可以取slice1的地址。

这样的输出结果是:

{A} {B} {C}
&{A} &{B} &{C}
复制代码

二、多重赋值需要掌握的易错点

看下面这段代码,请问输出结果是什么?

package main
​
import (
    "fmt"
)
​
func main() {
    i := 0
    s := []string{"A", "B", "C", "D"}
    i, s[i+1] = 2, "Z"
    fmt.Printf("s: %v \n", s)
}
​
复制代码

答案是:

s: [A Z C D] 
复制代码

为什么呢?

其实我们的多重赋值是按步骤进行的,不是同时进行,而是有前后顺序:

第一个阶段是估值阶段,然后是实施阶段:

比如:

a, b = b, a
复制代码

下面是估值阶段:

// 估值阶段
P0 := &a; P1 := &b
R0 := a; R1 := b
复制代码

然后是实施阶段:

// 最基本形式:*P0, *P1 = R0, R1
// 实施阶段
*P0 = R0
*P1 = R1
复制代码

实施阶段中的赋值操作并不会对估值阶段的结果造成影响。

而且,我们这里先计算=左边的索引表达式或取址表达式,然后计算=右边的表达式。再之后就是进行赋值操作。

所以我们这里先计算索引表达式i+1,然后进行赋值运算。

おすすめ

転載: juejin.im/post/7035519814785302536