【更多流程控制】1. defer语句

Go语言-defer语句

    与select语句同样,Go语言中的defer语句也很是独特,并且比前者有过之而无不及。defer语句仅能被放置在函数或方法中。它由关键字defer和一个调用表达式组成。注意,这里的调用表达式所表示的既不能是对Go语言内建函数的调用也不能是对Go语言标准库代码包unsafe中的那些函数的调用。实际上,知足上述条件的调用表达式被称为表达式语句。请看下面的示例:函数

func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    return ioutil.ReadAll(file)
}

    函数readFile的功能是读出指定文件或目录(如下统称为文件)自己的内容并将其返回,同时当有错误发生时当即向调用方报告。其中,osioutil(导入路径是io/ioutil)表明的都是Go语言标准库中的代码包。请注意这个函数中的倒数第二条语句。咱们在打开指定文件且未发现有错误发生以后,紧跟了一条defer语句。其中携带的表达式语句表示的是对被打开文件的关闭操做。注意,当这条defer语句被执行的时候,其中的这条表达式语句并不会被当即执行。它的确切的执行时机是在其所属的函数(这里是readFile)的执行即将结束的那个时刻。也就是说,在readFile函数真正结束执行的前一刻,file.Close()才会被执行。这也是defer语句被如此命名的缘由。咱们在结合上下文以后就能够看出,语句defer file.Close()的含义是在打开文件并读取其内容后及时地关闭它。该语句能够保证在readFile函数将结果返回给调用方以前,那个文件或目录必定会被关闭。这其实是一种很是便捷和有效的保险措施。
   
    更为关键的是,不管readFile函数正常地返回告终果仍是因为在其执行期间有运行时恐慌发生而被剥夺了流程控制权,其中的file.Close()都会在该函数即将退出那一刻被执行。这就更进一步地保证了资源的及时释放。
   
    注意,当一个函数中存在多个defer语句时,它们携带的表达式语句的执行顺序必定是它们的出现顺序的倒序。下面的示例能够很好的证实这一点:spa

func deferIt() {
    defer func() {
        fmt.Print(1)
    }()
    defer func() {
        fmt.Print(2)
    }()
    defer func() {
        fmt.Print(3)
    }()
    fmt.Print(4)
}

    deferIt函数的执行会使标准输出上打印出4321。请你们猜想下面这个函数被执行时向标准输出打印的内容,并真正执行它以验证本身的猜想。最后论证一下本身的猜想为何是对或者错的。code

func deferIt2() {
    for i := 1; i < 5; i++ {
        defer fmt.Print(i)
    }
}

    最后,对于defer语句,我还有两个特别提示:
   
    1. defer携带的表达式语句表明的是对某个函数或方法的调用。这个调用可能会有参数传入,好比:fmt.Print(i + 1)。若是表明传入参数的是一个表达式,那么在defer语句被执行的时候该表达式就会被求值了。注意,这与被携带的表达式语句的执行时机是不一样的。请揣测下面这段代码的执行:ci

func deferIt3() {
    f := func(i int) int {
        fmt.Printf("%d ",i)
        return i * 10
    }
    for i := 1; i < 5; i++ {
        defer fmt.Printf("%d ", f(i))
    }
}

    它在被执行以后,标准输出上打印出1 2 3 4 40 30 20 10 。
   
    2. 若是defer携带的表达式语句表明的是对匿名函数的调用,那么咱们就必定要很是警戒。请看下面的示例:资源

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}

    deferIt4函数在被执行以后标出输出上会出现5555,而不是4321。缘由是defer语句携带的表达式语句中的那个匿名函数包含了对外部(确切地说,是该defer语句以外)的变量的使用。注意,等到这个匿名函数要被执行(且会被执行4次)的时候,包含该defer语句的那条for语句已经执行完毕了。此时的变量i的值已经变为了5。所以该匿名函数中的打印函数只会打印出5。正确的用法是:把要使用的外部变量做为参数传入到匿名函数中。修正后的deferIt4函数以下:string

func deferIt4() {
    for i := 1; i < 5; i++ {
        defer func(n int) {
            fmt.Print(n)
        }(i)
    }
}

    请你们自行验证一下它的正确性。io

 

总结:class

1. defer表达式在其外函数运行结束前的最后一刻执行。import

2.注意,当一个函数中存在多个defer语句时,它们携带的表达式语句的执行顺序必定是它们的出现顺序的倒序。匿名函数

(defer后的函数放在栈中,在函数运行到最后在逐条出栈运行)

 

扩展:打印出斐波那契数列的前10个数

package main

import (
    "fmt"
)

func main() {
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", fibonacci(i))
	}
}

func fibonacci(num int) int {
	if num == 0 {
		return 0
	}
	if num < 2 {
		return 1
	}
	return fibonacci(num-1) + fibonacci(num-2)
}


使用 defer语句,逆序打出上面的结果

package main

import (
    "fmt"
)

func main() {
    var r int
	for i := 0; i < 10; i++ {
	    r = fibonacci(i)
		fmt.Printf("%d ", r)
		defer func(n int){
		    fmt.Printf("%d ", n)
		}(r)
	}
}

func fibonacci(num int) int {
	if num == 0 {
		return 0
	}
	if num < 2 {
		return 1
	}
	return fibonacci(num-1) + fibonacci(num-2)
}

输出结果以下

第一次输出结果:0 1 1 2 3 5 8 13 21 34

第二次输出结果:0 1 1 2 3 5 8 13 21 34 34 21 13 8 5 3 2 1 1 0
相关文章
相关标签/搜索