2017年国家公务员考试行测备考:defer的踩坑汇总

原文链接:

defer 语句用于延迟函数调用。每次将一个函数压入堆栈时,都会在函数返回之前将延迟的函数取出并执行。延迟函数可以有参数:

在日常开发中,使用不当很容易造成意想不到的“坑”。下面我对常规使用场景中defer问题可能存在的陷阱进行了总结。

释放资源

defer 语句用于延迟函数调用。每次将一个函数压入堆栈时,都会在函数返回之前将延迟的函数取出并执行。延迟函数可以有参数:

defer语句正是函数退出时执行的语句,所以使用defer可以轻松处理资源释放、句柄关闭等问题。

package main
import (
	"fmt"
	"os"
)
func fileSize(filename string) int64 {
	f, err := os.Open(filename)
	if err != nil {
		return 0
	}
	// 延迟调用Close, 此时Close不会被调用
	defer f.Close()
	info, err := f.Stat()
	if err != nil {
		// defer机制触发, 调用Close关闭文件
		return 0
	}
	size := info.Size()
	// defer机制触发, 调用Close关闭文件
	return size
}
func main() {
	fmt.Println(fileSize("demo.txt"))
}

变量捕获

defer中的变量会提前被捕获,后续修改不会影响捕获的值,例如:

package main
import (
	"fmt"
)
func main() {
	i := 0
	defer fmt.Println("Defer运行值:", i)
	i = 10 // 这里虽然修改了值,但是不会影响上面的i值
	fmt.Println("最后输出值:", i)
}

生成的 defer 语句中打印的值是修改前的值。 :

最后输出值:10

延迟运行值:0

变量名返回值

在defer中修改具体变量名的返回值时,会影响函数的实际返回值。继续举例:

package main
import (
    "fmt"
)
func ShowDefer() {
    fmt.Println("最后输出值:", deferValue())
}
func deferValue() (ret int) { // 注意这里返回res变量值
    ret = 0
    defer func() { // 会直接修改栈中对应的返回值
        ret += 10
        fmt.Println("Defer 运行值:", ret)
    }()
    ret = 2
    fmt.Println("Ret重置值:", ret)
    return //返回的ret最后是 其实是本次2+上面的ret+=10的结果
}
func main() {
    ShowDefer()
}
//Ret重置值: 2
//Defer 运行值: 12
//最后输出值: 12

非变量名返回值

当函数为非特定名称返回值时,defer不能影响返回值(因为返回时对应的返回值已经入栈),继续举例:

package main
import (
    "fmt"
)
func ShowDefer() {
    fmt.Println("最后输出值:", deferValue())
}
func deferValue() int { // 非命名变量返回
    ret := 0
    defer func() {
    ret += 10
    fmt.Println("Defer 运行值:", ret)
}()
    ret = 2
    return ret // 这里直接返回ret2
}
func main() {
    ShowDefer()
}
//Defer 运行值: 12
//最后输出值: 2

经过以上实战了解后,我们再来看看以下笔试题:

笔试题 1

package main
import "fmt"
func f() (result int) {
    defer func() {
    result *= 7
}()
    return 3
}
func main() {
    fmt.Println(f())
}

问题分析:这里return先给result赋值3,然后执行defer,result变成21,最后返回21。

笔试题2

package main
import "fmt"
func f() int {
    result := 3
    defer func() {
        result *= 7
    }()
    return result
}
func main() {
    fmt.Println(f())
} 

问题分析:这里return确定返回值3,然后defer修改结果,最后函数返回return确定的返回值3。

书面问题 3

package main
import "fmt"
// 多个defer
func multiDefer() {
    for i := 3; i > 0; i-- {
    defer func(n int) {
        fmt.Print(n, " ")
    }(i)
    }
    for i := 33; i > 30; i-- {
        defer fmt.Print(i, " ")
    }
}
func main() {
    multiDefer()
}  

问题分析:多个defer函数逆序执行,这里的输出是31 32 33 1 2 3。

笔试题 4

package main
import "fmt"
var fun func() string
func main() {
    fmt.Println("hello monkey")
    defer fun()
} 

问题分析:由于这里defer指定的func为nil,所以会panic。

问题 5

package main
import "fmt"
func main() {
    for i := 3; i > 0; i-- {
        defer func() {
            fmt.Print(i, " ")
            }()
    }
} 

问题分析:这是一个极易踩坑的地方。由于defer调用的func没有参数,所以在执行的时候,i已经是0了(倒序3 2 1,当最后一个i=1时,i–结果最后是0) ,所以这里输出 3 个零。

如果你不明白呢?

package main
import "fmt"
func main() {
    for i := 3; i > 1; i-- { // 循环满足条件的是 3 2,
        defer func() { // 因为func 没有参数,defer运行最后i--即 2-- 结果为 1
            fmt.Print(i, " ") // 循环2次 结果均为 1
        }()
    }
}//输出 1 1  

按照常规思路,应该是这样的:

package main
import "fmt"
func main() {
    for i := 3; i > 0; i-- {
    defer func(i int) {
        fmt.Print(i, " ")
        }(i)
    }
} 

感兴趣的朋友可以细细品下。

© 版权声明
THE END
喜欢就支持一下吧
点赞126 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片