mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
ch5-08 zh2tw
This commit is contained in:
parent
0374afd706
commit
41109c116f
@ -1,8 +1,8 @@
|
||||
## 5.8. Deferred函數
|
||||
|
||||
在findLinks的例子中,我们用http.Get的输出作为html.Parse的输入。只有url的内容的确是HTML格式的,html.Parse才可以正常工作,但实际上,url指向的内容很丰富,可能是图片,纯文本或是其他。将这些格式的内容传递给html.parse,会产生不良后果。
|
||||
在findLinks的例子中,我們用http.Get的輸出作爲html.Parse的輸入。隻有url的內容的確是HTML格式的,html.Parse才可以正常工作,但實際上,url指向的內容很豐富,可能是圖片,純文本或是其他。將這些格式的內容傳遞給html.parse,會産生不良後果。
|
||||
|
||||
下面的例子获取HTML页面并输出页面的标题。title函数会检查服务器返回的Content-Type字段,如果发现页面不是HTML,将终止函数运行,返回错误。
|
||||
下面的例子獲取HTML頁面併輸出頁面的標題。title函數會檢査服務器返迴的Content-Type字段,如果發現頁面不是HTML,將終止函數運行,返迴錯誤。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/title1
|
||||
@ -32,7 +32,7 @@ func title(url string) error {
|
||||
}
|
||||
```
|
||||
|
||||
下面展示了运行效果:
|
||||
下面展示了運行效果:
|
||||
|
||||
```powershell
|
||||
$ go build gopl.io/ch5/title1
|
||||
@ -44,11 +44,11 @@ $ ./title1 https://golang.org/doc/gopher/frontpage.png
|
||||
title: https://golang.org/doc/gopher/frontpage.png has type image/png, not text/html
|
||||
```
|
||||
|
||||
resp.Body.close调用了多次,这是为了确保title在所有执行路径下(即使函数运行失败)都关闭了网络连接。随着函数变得复杂,需要处理的错误也变多,维护清理逻辑变得越来越困难。而Go语言独有的defer机制可以让事情变得简单。
|
||||
resp.Body.close調用了多次,這是爲了確保title在所有執行路徑下(卽使函數運行失敗)都關閉了網絡連接。隨着函數變得複雜,需要處理的錯誤也變多,維護清理邏輯變得越來越睏難。而Go語言獨有的defer機製可以讓事情變得簡單。
|
||||
|
||||
你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
|
||||
你隻需要在調用普通函數或方法前加上關鍵字defer,就完成了defer所需要的語法。當defer語句被執行時,跟在defer後面的函數會被延遲執行。直到包含該defer語句的函數執行完畢時,defer後的函數才會被執行,不論包含defer語句的函數是通過return正常結束,還是由於panic導致的異常結束。你可以在一個函數中執行多條defer語句,它們的執行順序與聲明順序相反。
|
||||
|
||||
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。在下面的代码中,一条defer语句替代了之前的所有resp.Body.Close
|
||||
defer語句經常被用於處理成對的操作,如打開、關閉、連接、斷開連接、加鎖、釋放鎖。通過defer機製,不論函數邏輯多複雜,都能保證在任何執行路徑下,資源被釋放。釋放資源的defer應該直接跟在請求資源的語句後。在下面的代碼中,一條defer語句替代了之前的所有resp.Body.Close
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/title2
|
||||
@ -71,7 +71,7 @@ func title(url string) error {
|
||||
}
|
||||
```
|
||||
|
||||
在处理其他资源时,也可以采用defer机制,比如对文件的操作:
|
||||
在處理其他資源時,也可以采用defer機製,比如對文件的操作:
|
||||
|
||||
```Go
|
||||
io/ioutil
|
||||
@ -86,7 +86,7 @@ func ReadFile(filename string) ([]byte, error) {
|
||||
}
|
||||
```
|
||||
|
||||
或是处理互斥锁(9.2章)
|
||||
或是處理互斥鎖(9.2章)
|
||||
|
||||
```Go
|
||||
var mu sync.Mutex
|
||||
@ -98,7 +98,7 @@ func lookup(key string) int {
|
||||
}
|
||||
```
|
||||
|
||||
调试复杂程序时,defer机制也常被用于记录何时进入和退出函数。下例中的bigSlowOperation函数,直接调用trace记录函数的被调情况。bigSlowOperation被调时,trace会返回一个函数值,该函数值会在bigSlowOperation退出时被调用。通过这种方式, 我们可以只通过一条语句控制函数的入口和所有的出口,甚至可以记录函数的运行时间,如例子中的start。需要注意一点:不要忘记defer语句后的圆括号,否则本该在进入时执行的操作会在退出时执行,而本该在退出时执行的,永远不会被执行。
|
||||
調試複雜程序時,defer機製也常被用於記録何時進入和退出函數。下例中的bigSlowOperation函數,直接調用trace記録函數的被調情況。bigSlowOperation被調時,trace會返迴一個函數值,該函數值會在bigSlowOperation退出時被調用。通過這種方式, 我們可以隻通過一條語句控製函數的入口和所有的出口,甚至可以記録函數的運行時間,如例子中的start。需要註意一點:不要忘記defer語句後的圓括號,否則本該在進入時執行的操作會在退出時執行,而本該在退出時執行的,永遠不會被執行。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/trace
|
||||
@ -118,7 +118,7 @@ func trace(msg string) func() {
|
||||
}
|
||||
```
|
||||
|
||||
每一次bigSlowOperation被调用,程序都会记录函数的进入,退出,持续时间。(我们用time.Sleep模拟一个耗时的操作)
|
||||
每一次bigSlowOperation被調用,程序都會記録函數的進入,退出,持續時間。(我們用time.Sleep模擬一個耗時的操作)
|
||||
|
||||
```powershell
|
||||
$ go build gopl.io/ch5/trace
|
||||
@ -126,9 +126,9 @@ $ ./trace
|
||||
2015/11/18 09:53:26 enter bigSlowOperation
|
||||
2015/11/18 09:53:36 exit bigSlowOperation (10.000589217s)
|
||||
```
|
||||
我们知道,defer语句中的函数会在return语句更新返回值变量后再执行,又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量,所以,对匿名函数采用defer机制,可以使其观察函数的返回值。
|
||||
我們知道,defer語句中的函數會在return語句更新返迴值變量後再執行,又因爲在函數中定義的匿名函數可以訪問該函數包括返迴值變量在內的所有變量,所以,對匿名函數采用defer機製,可以使其觀察函數的返迴值。
|
||||
|
||||
以double函数为例:
|
||||
以double函數爲例:
|
||||
|
||||
```Go
|
||||
func double(x int) int {
|
||||
@ -136,7 +136,7 @@ func double(x int) int {
|
||||
}
|
||||
```
|
||||
|
||||
我们只需要首先命名double的返回值,再增加defer语句,我们就可以在double每次被调用时,输出参数以及返回值。
|
||||
我們隻需要首先命名double的返迴值,再增加defer語句,我們就可以在double每次被調用時,輸出參數以及返迴值。
|
||||
|
||||
```Go
|
||||
func double(x int) (result int) {
|
||||
@ -148,9 +148,9 @@ _ = double(4)
|
||||
// "double(4) = 8"
|
||||
```
|
||||
|
||||
可能doulbe函数过于简单,看不出这个小技巧的作用,但对于有许多return语句的函数而言,这个技巧很有用。
|
||||
可能doulbe函數過於簡單,看不出這個小技巧的作用,但對於有許多return語句的函數而言,這個技巧很有用。
|
||||
|
||||
被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值:
|
||||
被延遲執行的匿名函數甚至可以脩改函數返迴給調用者的返迴值:
|
||||
|
||||
```Go
|
||||
func triple(x int) (result int) {
|
||||
@ -160,7 +160,7 @@ func triple(x int) (result int) {
|
||||
fmt.Println(triple(4)) // "12"
|
||||
```
|
||||
|
||||
在循环体中的defer语句需要特别注意,因为只有在函数执行完毕后,这些被延迟的函数才会执行。下面的代码会导致系统的文件描述符耗尽,因为在所有文件都被处理之前,没有文件会被关闭。
|
||||
在循環體中的defer語句需要特别註意,因爲隻有在函數執行完畢後,這些被延遲的函數才會執行。下面的代碼會導致繫統的文件描述符耗盡,因爲在所有文件都被處理之前,沒有文件會被關閉。
|
||||
|
||||
```Go
|
||||
for _, filename := range filenames {
|
||||
@ -174,7 +174,7 @@ for _, filename := range filenames {
|
||||
}
|
||||
```
|
||||
|
||||
一种解决方法是将循环体中的defer语句移至另外一个函数。在每次循环时,调用这个函数。
|
||||
一種解決方法是將循環體中的defer語句移至另外一個函數。在每次循環時,調用這個函數。
|
||||
|
||||
```Go
|
||||
for _, filename := range filenames {
|
||||
@ -192,7 +192,7 @@ func doFile(filename string) error {
|
||||
}
|
||||
```
|
||||
|
||||
下面的代码是fetch(1.5节)的改进版,我们将http响应信息写入本地文件而不是从标准输出流输出。我们通过path.Base提出url路径的最后一段作为文件名。
|
||||
下面的代碼是fetch(1.5節)的改進版,我們將http響應信息寫入本地文件而不是從標準輸出流輸出。我們通過path.Base提出url路徑的最後一段作爲文件名。
|
||||
|
||||
```Go
|
||||
gopl.io/ch5/fetch
|
||||
@ -221,6 +221,6 @@ func fetch(url string) (filename string, n int64, err error) {
|
||||
}
|
||||
```
|
||||
|
||||
对resp.Body.Close延迟调用我们已经见过了,在此不做解释。上例中,通过os.Create打开文件进行写入,在关闭文件时,我们没有对f.close采用defer机制,因为这会产生一些微妙的错误。许多文件系统,尤其是NFS,写入文件时发生的错误会被延迟到文件关闭时反馈。如果没有检查文件关闭时的反馈信息,可能会导致数据丢失,而我们还误以为写入操作成功。如果io.Copy和f.close都失败了,我们倾向于将io.Copy的错误信息反馈给调用者,因为它先于f,close发生,更有可能接近问题的本质。
|
||||
對resp.Body.Close延遲調用我們已經見過了,在此不做解釋。上例中,通過os.Create打開文件進行寫入,在關閉文件時,我們沒有對f.close采用defer機製,因爲這會産生一些微妙的錯誤。許多文件繫統,尤其是NFS,寫入文件時發生的錯誤會被延遲到文件關閉時反饋。如果沒有檢査文件關閉時的反饋信息,可能會導致數據丟失,而我們還誤以爲寫入操作成功。如果io.Copy和f.close都失敗了,我們傾向於將io.Copy的錯誤信息反饋給調用者,因爲它先於f,close發生,更有可能接近問題的本質。
|
||||
|
||||
**练习5.18:**不修改fetch的行为,重写fetch函数,要求使用defer机制关闭文件。
|
||||
**練習5.18:**不脩改fetch的行爲,重寫fetch函數,要求使用defer機製關閉文件。
|
Loading…
Reference in New Issue
Block a user