在面试的过程中, 如果恰好遇到对方日常也使用 Go 做为主力语言, 我会选择一些简单而可扩展的问题交流下双方对 Go 的熟悉程度.
我喜欢的一个问题是让面试者告诉我下述代码的运行结果:
func main() { for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() } time.Sleep(time.Second) } 正确的答案应该是: 乱序输出三个数字. 对于三种错误答案: 输出 1, 2, 3; 输出三个数字; 乱序输出 1, 2, 3; 都可以通过反问再给予一次机会.
进一步的, 我们可以询问如何让其至少将 1, 2, 3 都输出一次. 大多数时候, 我们的得到的答案会是将 i 做为参数传入. 此时我喜欢再追问, 下述代码中 i := i 的写法是否正确.
func main() { for i := 0; i < 3; i++ { i := i go func() { fmt.Println(i) }() } time.Sleep(time.Second) } 我并不认为这是一个 Language Lawyer 问题, 由于 Go 中 for 循环的特殊实现方式, i := i 这种方式在 Go 中是普遍存在的.
极少数情况下, 我们可以再讨论下上述例子的原因, 允许面试者有更大的发挥机会. 其中包括的点有:
- Go 并不保证先启动的 goroutine 先执行
- Go 中 for 循环的实现是 one-instance-per-loop, 而不是 one-instance-per-iteration.
我们在下述例子中看到, i 和 v 的内存地址始终未曾改变:
~ cat main.go func main() { nums := []int{1, 2, 3} for i, v := range nums { fmt.Println(&i, &v) } } ~ go run main.go 0x1400009a018 0x1400009a020 0x1400009a018 0x1400009a020 0x1400009a018 0x1400009a020 - 闭包(closure)可能以值(by value)或者地址(by reference)的形式引用外部变量; 当引用 for 循环的中变量时, 是以地址的方式
- Go 允许在 inner block 中定义重名的变量, 下述代码虽然不好但合法
~ cat main.go | grep -A 7 "func fnVarScope" func fnVarScope() { s := "hello world" { s := 10 fmt.Println("s:", s) } fmt.Println("s:", s) } ~ go run main.go s: 10 s: hello world 
