golang中defer
的用途是什么?语言文档说它是在周围函数返回时执行的。为什么不把代码放在给定函数的末尾?
答案 0 :(得分:37)
我们通常使用defer
来关闭或取消分配资源。
周围函数在返回之前执行所有延迟函数调用,即使它发生混乱。如果您只是在周围函数的末尾放置一个函数调用,则在发生混乱时会跳过它。
此外,延迟函数调用可以通过调用recover
内置函数来处理恐慌。这不能通过函数末尾的普通函数调用来完成。
每个延迟调用都被置于堆栈中,并在周围函数结束时以相反的顺序执行。相反的顺序有助于正确释放资源。
必须达到defer
语句才能调用函数。
您可以将其视为实施try-catch-finally
块的另一种方式。
关闭try-finally
:
func main() {
f, err := os.Create("file")
if err != nil {
panic("cannot create file")
}
defer f.Close()
// no matter what happens here file will be closed
// for sake of simplicity I skip checking close result
fmt.Fprintf(f,"hello")
}
关闭和恐慌处理,如try-catch-finally
func main() {
defer func() {
msg := recover()
fmt.Println(msg)
}()
f, err := os.Create(".") // . is a current directory
if err != nil {
panic("cannot create file")
}
defer f.Close()
// no matter what happens here file will be closed
// for sake of simplicity I skip checking close result
fmt.Fprintf(f,"hello")
}
try-catch-finally的好处是没有嵌套的块和变量范围。这简化了周围功能的结构。
就像finally块一样,延迟函数调用也可以修改返回值,如果它们可以到达返回的数据。
func yes() (text string) {
defer func() {
text = "no"
}()
return "yes"
}
func main() {
fmt.Println(yes())
}
答案 1 :(得分:4)
嗯,并不总能保证您的代码可能会到达函数的末尾(例如,错误或某些其他条件可能会迫使您在函数结束之前返回)。 defer语句确保分配给它的任何函数都会被执行,即使函数发生混乱或代码在函数结束之前返回良好。
defer语句还有助于保持代码清洁。在函数esp中有多个return语句的情况下。当一个人需要在返回之前释放资源时(例如,假设你在函数开始时有一个公开调用来访问资源 - 为了避免资源泄漏,必须在函数返回之前调用相应的close。并且说你的函数有多个return语句,可能是针对包括错误检查在内的不同条件。在这种情况下,没有延迟,通常会在每个return语句之前为该资源调用close。 defer语句确保无论函数返回的位置如何,总是调用传递给它的函数,从而使您免于进行过多的内务管理工作。
也可以在同一功能中多次调用延迟。例如:如果您通过函数分配了不同的资源,需要在返回之前最终释放,那么您可以在分配后调用每个资源的延迟,并且这些函数的执行顺序与调用它们的顺序相反。当函数退出时。
答案 2 :(得分:2)
使用defer
的主要好处 - 无论函数如何返回,都会以任何方式调用它。如果发生特殊情况,将调用延迟函数。
所以它提供了很好的东西:
在panic
之后恢复。这允许yes实现try ... catch
行为。
在正常退出之前不要忘记清理(关闭文件,空闲内存等)。您可以打开一些资源,然后在退出之前必须关闭它。但是函数可以有几个退出点 - 所以你必须在每个返回点的中添加释放。这在维护方面非常繁琐。或者您只能放置一个延迟语句 - 资源将自动释放。
答案 3 :(得分:1)
defer
语句将函数的执行推迟到 周围的函数返回。
此示例演示了defer
的功能:
func elapsed(what string) func() {
start := time.Now()
fmt.Println("start")
return func() {
fmt.Printf("%s took %v\n", what, time.Since(start))
}
}
func main() {
defer elapsed("page")()
time.Sleep(time.Second * 3)
}
出局:
start
page took 3s
答案 4 :(得分:1)
这里已经有很好的答案。我想再提一个用例。
func BillCustomer(c *Customer) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.Bill(); err != nil {
return err
}
if err := c.Notify(); err != nil {
return err
}
// ... do more stuff ...
return nil
}
此示例中的defer
确保无论BillCustomer
如何返回,mutex
都将在unlocked
返回之前为BillCustomer
。这非常有用,因为如果没有defer
,您将不得不记住在unlock
函数可能会出现的每个位置mutex
。{p1
ref。
答案 5 :(得分:0)