面试题答案
一键面试可能遇到的问题
- 延迟调用的变量捕获问题:defer 语句中的闭包会捕获其定义时的变量,而不是调用时的变量。 例如:
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}
}
上述代码期望输出 2 1 0
,但实际输出是 3 3 3
。原因是 defer 中的闭包捕获了循环变量 i
,在 defer 语句执行时,i
的值已经是 3
。
- 资源释放顺序问题:当多个 defer 语句与闭包结合时,如果不注意,可能导致资源释放顺序不符合预期。 例如:
package main
import "fmt"
func main() {
file1 := "file1"
file2 := "file2"
defer func() {
fmt.Println("Close", file1)
}()
defer func() {
fmt.Println("Close", file2)
}()
fmt.Println("Processing...")
}
这里虽然先 defer 关闭 file1
,后 defer 关闭 file2
,但实际输出是 Processing... Close file2 Close file1
,因为 defer 是后进先出(LIFO)执行的。如果 file2
的关闭依赖 file1
,就可能出问题。
避免方法
- 解决延迟调用的变量捕获问题:
- 传值方式:将需要的变量以参数形式传入闭包。
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func(j int) {
fmt.Println(j)
}(i)
}
}
这样会输出 2 1 0
,因为每次迭代都创建了一个新的闭包,并传入了当时 i
的值。
- 使用短变量声明:在 defer 内部使用短变量声明 :=
重新定义变量。
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer func() {
j := i
fmt.Println(j)
}()
}
}
同样会输出 2 1 0
,通过在闭包内重新定义变量,避免了对外部循环变量的捕获。
- 解决资源释放顺序问题:
- 调整 defer 语句顺序:根据资源依赖关系,调整 defer 语句的书写顺序,使依赖关系正确。如果
file2
依赖file1
,则先 defer 关闭file2
,后 defer 关闭file1
。
- 调整 defer 语句顺序:根据资源依赖关系,调整 defer 语句的书写顺序,使依赖关系正确。如果
package main
import "fmt"
func main() {
file1 := "file1"
file2 := "file2"
defer func() {
fmt.Println("Close", file1)
}()
defer func() {
fmt.Println("Close", file2)
}()
fmt.Println("Processing...")
}
- **使用显式的释放函数调用**:不依赖 defer 的 LIFO 特性,在函数结束时显式调用资源释放函数,并按照正确顺序进行调用。
package main
import "fmt"
func closeFile1() {
fmt.Println("Close file1")
}
func closeFile2() {
fmt.Println("Close file2")
}
func main() {
fmt.Println("Processing...")
closeFile2()
closeFile1()
}