Go语言学习查缺补漏ing Day10

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

Go语言学习查缺补漏ing Day10

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

零、前言

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
go复制代码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])
}

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

1
2
go复制代码{A} {B} {C}
&{A} &{B} &{C}

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

1
2
go复制代码{A} {B} {C}
&{C} &{C} &{C}

为什么会出现这种情况?

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

有什么解决办法?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
go复制代码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])
}
1
2
go复制代码{A} {B} {C}
{A} {B} {C}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
go复制代码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的地址。

这样的输出结果是:

1
2
go复制代码{A} {B} {C}
&{A} &{B} &{C}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码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)
}

答案是:

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

为什么呢?

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

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

比如:

1
go复制代码a, b = b, a

下面是估值阶段:

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

然后是实施阶段:

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

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

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

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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%