前言
Go中控制流程的机制是普通的,和其他语言的流程控制相似: if, for, switch, goto。它也有go语句,可以使得代码在一个单独的goroutine中运行。现在,我将讨论和其他语言不同的部分:defer, panic, 和recover。
defer操作会将函数放到了一个list之中。存在此list之中的函数会在所在函数return之后执行。defer通常用于处理各种不同的清理操作。
例如,我们来看一个函数,此函数会打开两个文件,并将一个文件的内容复制到另一个文件之中:
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
dst, err := os.Create(dstName)
if err != nil {
return
}
written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
这个函数可以正常运行,但是是有bug的。在调用os.Create(dstName)创建文件失败的情况下,此函数会在没有关闭打开的源文件的情况下,直接返回。这个问题可以非常简单解决,在函数中第二个return的前面加一行代码src.Close(),用于关闭源文件。但是在代码更复杂的时候,这种问题可能不是那么容易就可以发现和解决的。通过defer操作,就可以解决此类问题。
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
通过defer,我们可以在打开文件的时候,就思考关闭文件。并且可以保证在无论函数中多少个return的情况下,都可以在函数返回的时候,关闭文件。
参考原则
defer操作是非常直接和可预测的。有三个简单的原则:
1.defer的函数参数,会在defer调用的时候就确定。
例子下面的例子,由于变量i的初始化值为1,在defer的函数调用时,此值i仍然为1。
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
defer的函数可以使一个对象的方法,此时可能会让人难以理解。
在defer的函数的对象是一个对象的时候(非指针),下面函数会打印DeLorean DMC-12
type Car struct {
model string
}
func (c Car) PrintModel() {
fmt.Println(c.model)
}
func main() {
c := Car{model: "DeLorean DMC-12"}
defer c.PrintModel()
c.model = "Chevrolet Impala"
}
在defer的函数的对象是一个指针,下面函数会打印Chevrolet Impala
type Car struct {
model string
}
func (c *Car) PrintModel() {
fmt.Println(c.model)
}
func main() {
c := &Car{model: "DeLorean DMC-12"}
defer c.PrintModel()
c.model = "Chevrolet Impala"
}
在defer的函数是一个方法的时候,背后发生了如下的变换,函数接受一个对象的时候,传递一个对象作为参数,是复制了一份。所以在使用对象的时候,打印的是传入对象的model值,而使用指针调用方法的时候,指针指向的对象model发生了修改,所以打印的是修改后的值。
func (receiver Type) f(input) result -> func(recevier Type, input) result
- 保存
defer的函数list,是一个栈,顺序为 Last In First Out
下面函数调用的时候,打印值为3210。
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
defer的函数可以读取并修改函数的返回值
下面函数的返回值是i,而defer的函数可以读取和修改变量i,此函数会在return之后执行,所以此函数的返回值实际为2。
func c() (i int) {
defer func() { i++ }()
return 1
}