for-range陷阱


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {

slice := []int64{1, 2, 3, 4, 5}
output := make([]*int64, 0, len(slice))
for _, v := range slice {
output = append(output, &v)
}
for _, x := range output {
fmt.Println(*x)
}
}


输出:

1
2
3
4
5
5
5
5
5
5




for .. range xxx 实际上迭代的是 xxx 的一个副本


本部分内容来自 老貘 Go 细节和小技巧 101


for i, v = range aContiner 实际上是迭代 aContainer
的一个副本

例如,下面这个程序将打印 123 而不是 189

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main() {
var a = [...]int{1, 2, 3}

for i, n := range a {
if i == 0 {
a[1], a[2] = 8, 9
}
print(n)
}
}

如果被遍历的容器是一个大的数组,那么复制成本就会很高。


有一个例外:如果 for-range 中的第二个迭代变量被省略或忽略,那么被遍历的容器将
不会被复制(因为没有必要进行复制)。例如,在下面的两个循环中,数组 a 没有被复
制:

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main() {
var a = [...]int{1, 2, 3}

for i := range a {
print(i)
}
for i, _ := range a {
print(i)
}
}

在 Go 中,一个数组拥有其元素,但一个切片只是引用着其元素。

在 Go 中,值的复制都是浅拷贝,复制一个值不会复制它所引用的值。所以复制一个切片不会复制其元素。

这可以反映在下面的程序中,该程序打印出 189

1
2
3
4
5
6
7
8
9
10
11
12
package main

func main() {
var s = []int{1, 2, 3}

for i, n := range s {
if i == 0 {
s[1], s[2] = 8, 9
}
print(n)
}
}




T,*T & 循环


[]T 还是 []*T, 这是一个问题

golang之用好指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

type field struct {
name string
}

func (p *field) print() {
fmt.Println(p.name)
}
func main() {
data1 := []*field{{"one"}, {"two"}, {"three"}}
for _, v := range data1 {
defer v.print()
}
data2 := []field{{"four"}, {"five"}, {"six"}}
for _, v := range data2 {
defer v.print()
}
}

这段程序的输出结果是:

1
2
3
4
5
6
six
six
six
three
two
one

这个程序定义了一个名为 field 的结构体,包含一个名为 name 的字符串属性,并为该结构体定义了一个方法 print() 用于输出 name 的值。

在 main() 函数中,首先创建了一个包含三个 field 类型指针的切片 data1 和一个包含三个 field 类型实例的切片 data2。然后分别使用 defer 关键字将 data1 和 data2 里的元素逆序输出。

需要注意,因为v.print(),v需要是一个指针。可以加一些调试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"

"github.com/davecgh/go-spew/spew"
)

type field struct {
name string
}

func (p *field) print() {

spew.Dump(p)
fmt.Println(p.name)
}

func main() {
data1 := []*field{{"one"}, {"two"}, {"three"}}
for _, v := range data1 {

defer v.print()
}
data2 := []field{{"four"}, {"five"}, {"six"}}
for _, v := range data2 {

defer v.print()
}
}

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(*main.field)(0x14000188180)({
name: (string) (len=3) "six"
})
six
(*main.field)(0x14000188180)({
name: (string) (len=3) "six"
})
six
(*main.field)(0x14000188180)({
name: (string) (len=3) "six"
})
six
(*main.field)(0x14000188140)({
name: (string) (len=5) "three"
})
three
(*main.field)(0x14000188130)({
name: (string) (len=3) "two"
})
two
(*main.field)(0x14000188120)({
name: (string) (len=3) "one"
})
one

可见在第一个for中,v的指针一样..因此,输出的顺序是 six, six, six, three, two, one。


如果改成func (p field) print(),即

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"

"github.com/davecgh/go-spew/spew"
)

type field struct {
name string
}

func (p field) print() {

spew.Dump(p)
fmt.Println(p.name)
}

func main() {
data1 := []*field{{"one"}, {"two"}, {"three"}}
for _, v := range data1 {

defer v.print()
}
data2 := []field{{"four"}, {"five"}, {"six"}}
for _, v := range data2 {

defer v.print()
}
}

则就是预期的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(main.field) {
name: (string) (len=3) "six"
}
six
(main.field) {
name: (string) (len=4) "five"
}
five
(main.field) {
name: (string) (len=4) "four"
}
four
(main.field) {
name: (string) (len=5) "three"
}
three
(main.field) {
name: (string) (len=3) "two"
}
two
(main.field) {
name: (string) (len=3) "one"
}
one




闭包的一些特性


闭包特性1:对于返回的每个闭包g()来说,不同的g()引用不同的x对应的数据对象。换句话说,变量x对应的数据对象对每个闭包来说都是相互独立的

闭包特性2:对于某个闭包函数来说,只要这不是一个匿名闭包,那么闭包函数可以一直访问x对应的数据对象,即使名称x已经消失(即不是一次性的,通过赋值给一个变量的形式,可以多次调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func Increase() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
i1 := Increase()
i2 := Increase()
fmt.Println(i1()) // 1
fmt.Println(i2()) // 1
fmt.Println(i1()) // 2
fmt.Println(i1()) // 3
}

这段程序会输出以下内容:

1
2
3
4
1
1
2
3

程序中定义了一个函数Increase(),该函数返回一个匿名函数。每次调用该匿名函数时,它会将n的值加1,并返回新的n值。

在main()函数中,使用Increase()函数创建了两个闭包i1和i2,并分别将它们赋值给变量i1和i2。i1和i2是两个独立的闭包,它们都有自己的n值。

接下来,依次调用三次i1()函数。由于i1()函数是一个闭包,因此每次调用它的时候都会将自己的n值加1,并返回新的n值。因此,第一次调用i1()函数输出1,第二次调用i1()函数输出2,第三次调用i1()函数输出3。

调用一次i2()函数,这个闭包的n值是独立的,所以输出1。




defer&闭包&变量


有值返回和无值返回~

Golang中的defer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main
import "fmt"
func func1() (i int) {
i = 1
defer func() {
i++
}()
i++
return
}
func func2() int {
i := 1
defer func() {
i++
}()
i++
return i
}
func func3() int {
i := 1
defer func(i int) {
i++
}(i)
i++
return i
}
func func4() (i int) {
i = 1
defer func(i int) {
i++
}(i)
i++
return
}
func func5() (i int) {
i = 1
defer fmt.Println("result =>", func() int { return i * 6 }())
i++
return
}
func main() {
fmt.Println(func1())
fmt.Println(func2())
fmt.Println(func3())
fmt.Println(func4())
fmt.Println(func5())
}

输出:

1
2
3
4
5
6
3
2
2
2
result => 6
2


添加一些辅助代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main

import "fmt"

// 等同于func2()
func func0() (k int) {
i := 1
defer func() {
i++
}()
i++
return i
}

func func1() (i int) {
i = 1
defer func() {
i++
fmt.Println("func1()中此时i为:", i)
}()
i++
return
}

func func2() int {
i := 1
defer func() {
i++
fmt.Println("func2()中此时i为:", i)
}()
i++
return i
}
func func3() int {
i := 1
defer func(i int) {
i++
fmt.Println("func3()中此时i为:", i)
}(i)
i++
return i
}
func func4() (i int) {
i = 1
defer func(i int) {
i++
fmt.Println("func4()中此时i为:", i)
}(i)
i++
return
}
func func5() (i int) {
i = 1
defer fmt.Println("result =>", func() int { return i * 6 }())
i++
return
}

func main() {
fmt.Println("func0:", func0())
println("-------------------")
fmt.Println("func1:", func1())
println("-------------------")
fmt.Println("func2:", func2())
println("-------------------")
fmt.Println("func3:", func3())
println("-------------------")
fmt.Println("func4:", func4())
println("-------------------")
fmt.Println("func5:", func5())
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func0: 2
-------------------
func1()中此时i为: 3
func1: 3
-------------------
func2()中此时i为: 3
func2: 2
-------------------
func3()中此时i为: 2
func3: 2
-------------------
func4()中此时i为: 2
func4: 2
-------------------
result => 6
func5: 2



defer 语句延迟执行的时刻是在函数返回之前。而在此之前,函数内部的变量可能会被修改。因此,要理解 defer 语句的执行顺序需要考虑以下几个问题:

defer 语句中的函数什么时候求值?
defer 语句中的函数什么时候执行?
return 语句什么时候执行?


  • 如果传参进defer后面的函数(无论是闭包func(){}(i)方式还是子方法f(i)方式,或是直接跟如fmt.Println(i)),defer回溯时均以当时传参时i的值去计算

  • 反之,defer回溯时,以最后i的值带入计算

  • return之后的语句先执行,defer后的语句后执行(所以defer可以影响返回值)

对于func5,可参考 几种写法之间的归类与区别

defer fmt.Println("in defer :", i)相当于

1
2
3
defer func(k int) {
fmt.Println(k)
}(i)

1
2
3
func f(k int){
fmt.Println(k)
}


tips:

但凡是类似

1
2
3
func() {
print(xxx)
}(xxx)

有传值进去的,和调用一个子函数

1
2
3
4

f(xxx string) {
print(xxx)
}

效果一样。值是隔离的,受保护的(参数为5种引用类型时除外)