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) } }
|
输出:
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()) fmt.Println(i2()) fmt.Println(i1()) fmt.Println(i1()) }
|
这段程序会输出以下内容:
程序中定义了一个函数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 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"
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种引用类型时除外)
↑
BTC Address:3NNxkM6ez7szsUAgTnK2VaF949LoGmXuBs