引言
本系列会列举一些在 Go 面试中常见的问题。
切片循环问题
For 循环在咱们日常编码中可能用的很多。在很多业务场景中咱们都须要用 for 循环解决。但 golang 中的 for 循环在应用上须要留神一些问题,大家可否遇到。先看下边这一段代码:
func testSlice() {a := []int64{1,2,3}
for _, v := range a {go func() {fmt.Println(v)
}()}
time.Sleep(time.Second)
}
output: 3 3 3
那么为什么会输入的是这个后果呢?
在 golang 的 for 循环中,循环外部创立的函数变量都是共享同一块内存地址,for 循环总是应用同一块内存去接管循环中的的 value 变量的值。不论循环多少次,value 的内存地址都是雷同的。咱们能够测试一下:
func testSliceWithAddress() {a := []int64{1,2,3}
for _, v := range a {go func() {fmt.Println(&v)
}()}
time.Sleep(time.Second)
}
output:
0xc0000b2008
0xc0000b2008
0xc0000b2008
合乎预期。如果大家比拟感兴趣的话能够去将这段代码的汇编打印进去,就能够发现循环的 v 始终在操作同一块内存。
同样的,在 slice 循环这块咱们还会遇见另一个乏味的中央,大家能够看看下边这段代码输入什么?
func testRange3() {a := []int64{1,2,3}
for _, v := range a {a = append(a, v)
}
fmt.Println(a)
}
这段代码的输入后果是:[1 2 3 1 2 3],为什么呢?因为 golang 在循环前会先拷贝一个 a,而后对新拷贝的 a 进行操作,所以循环的次数不会随着 append 而增多。
interface 和 nil 比拟
比方返回了一个空指针,但并不是一个空 interface
func testInterface() {doit := func(arg int) interface{} {var result * struct{} = nil
if arg > 0 {result = &struct{}{}}
return result
}
if res := doit(-1); res != nil {fmt.Println("result:", res)
}
}
输入后果为:result: <nil>,为什么呢?因为在 go 里边变量有类型和值两个属性,在比拟的时候也会比拟类型和值都雷同才会认为相等。代码中 result 的类型是指针,值是 nil,所以会有这样的输入。
可变参数是空接口类型
当参数的可变参数是空接口类型时,传入空接口的切片时须要留神参数开展的问题。
func testVolatile() {var a = []interface{}{1, 2, 3}
fmt.Println(a)
fmt.Println(a...)
}
输入后果为:
[1 2 3]
1 2 3
map 遍历时程序不固定
不要置信 map 的程序!
func testMap() {m := map[string]string{
"a": "a",
"b": "b",
"c": "c",
}
for k, v := range m {println(k, v)
}
}
具体起因大家能够看一下源码:map.go:mapiterinit,就会发现下边这个代码用来决定从哪开始遍历 map。另一个起因是 map 在某些特定状况下(例如扩容),会产生 key 的搬迁重组。而遍历的过程,就是按程序遍历 bucket,同时按程序遍历 bucket 中的 key。搬迁后,key 的地位产生了重大的变动,所以遍历 map 的后果就不可能按原来的程序了。
func mapiterinit(t *maptype, h *hmap, it *hiter) {
......
// decide where to start
r := uintptr(fastrand())
......
}
关注咱们
欢送对本系列文章感兴趣的读者订阅咱们的公众号,关注博主下次不迷路~