diff --git a/ch5/ch5-03.md b/ch5/ch5-03.md index dbbac2c..11c3f1a 100644 --- a/ch5/ch5-03.md +++ b/ch5/ch5-03.md @@ -1,3 +1,121 @@ ## 5.3. 多返迴值 -TODO +在Go中,一個函數可以返迴多個值。我們已經在之前例子中看到,許多標準庫中的函數返迴2個值,一個是期望得到的返迴值,另一個是函數出錯時的錯誤信息。下面的例子會展示如何編寫多返迴值的函數。 + +下面的程序是findlinks的改進版本。脩改後的findlinks可以自己發起HTTP請求,這樣我們就不必再運行fetch。因爲HTTP請求和解析操作可能會失敗,因此findlinks聲明了2個返迴值:鏈接列表和錯誤信息。一般而言,HTML的解析器可以處理HTML頁面的錯誤結點,構造出HTML頁面結構,所以解析HTML很少失敗。這意味着如果findlinks函數失敗了,很可能是由於I/O的錯誤導致的。 + +``` +gopl.io/ch5/findlinks2 +func main() { +for _, url := range os.Args[1:] { +links, err := findLinks(url) +if err != nil { +fmt.Fprintf(os.Stderr, "findlinks2: %v\n", +err) +continue +} +for _, link := range links { +fmt.Println(link) +} +} +} + +// findLinks performs an HTTP GET request for url, parses +the +// response as HTML, and extracts and returns the links. +func findLinks(url string) ([]string, error) { +resp, err := http.Get(url) +if err != nil { +return nil, err +} +if resp.StatusCode != http.StatusOK { +resp.Body.Close() +return nil, fmt.Errorf("getting %s: %s", url, +resp.Status) +} +doc, err := html.Parse(resp.Body) + +resp.Body.Close() +if err != nil { +return nil, fmt.Errorf("parsing %s as HTML: %v", +url, err) +} +return visit(nil, doc), nil +} +``` + +在findlinks中,有4處return語句,每一處return都返迴了一組值。前三處return,將http和html包中的錯誤信息傳遞給findlinks的調用者。第一處return直接返迴錯誤信息,其他兩處通過fmt.Errorf(§7.8)輸出詳細的錯誤信息。如果findlinks成功結束,最後的return語句將一組解析獲得的連接返迴給用戶。 + +在finallinks中,我們必鬚確保resp.Body被關閉,釋放網絡資源。雖然Go的垃圾迴收機製會迴收不被使用的內存,但是這不包括操作繫統層面的資源,比如打開的文件、網絡連接。因此我們必鬚顯式的釋放這些資源。 + +調用多返迴值函數時,返迴給調用者的是一組值,調用者必鬚顯式的將這些值分配給變量: + +``` +links, err := findLinks(url) +``` +如果某個值不被使用,可以將其分配給blank identifier: + +``` +links, _ := findLinks(url) // errors ignored +``` +一個函數內部可以將另一個有多返迴值的函數作爲返迴值,下面的例子展示了與findLinks有相同功能的函數,兩者的區别在於下面的例子先輸出參數: + +``` +func findLinksLog(url string) ([]string, error) { +log.Printf("findLinks %s", url) +return findLinks(url) +} +``` + +當你調用接受多參數的函數時,可以將一個返迴多參數的函數作爲該函數的參數。雖然這很少出現在實際生産代碼中,但這個特性在debug時很方便,我們隻需要一條語句就可以輸出所有的返迴值。下面的代碼是等價的: + +``` +log.Println(findLinks(url)) +links, err := findLinks(url) +log.Println(links, err) +``` + +準確的變量名可以傳達函數返迴值的含義。尤其在返迴值的類型都相同時,就像下面這樣: + +``` +func Size(rect image.Rectangle) (width, height int) +func Split(path string) (dir, file string) +func HourMinSec(t time.Time) (hour, minute, second int) +``` + +雖然良好的命名很重要,但你也不必爲每一個返迴值都取一個適當的名字。比如,按照慣例,函數的最後一個bool類型的返迴值表示函數是否運行成功,error類型的返迴值代表函數的錯誤信息,對於這些類似的慣例,我們不必思考合適的命名,它們都無需解釋。 + +如果一個函數將所有的返迴值都顯示的變量名,那麽該函數的return語句可以省略操作數。這稱之爲bare return。 + +``` +// CountWordsAndImages does an HTTP GET request for the +HTML +// document url and returns the number of words and images +in it. +func CountWordsAndImages(url string) (words, images int, +err error) { +resp, err := http.Get(url) +if err != nil { +return +} +doc, err := html.Parse(resp.Body) +resp.Body.Close() +if err != nil { +err = fmt.Errorf("parsing HTML: %s", err) +return +} +words, images = countWordsAndImages(doc) +return +} +func countWordsAndImages(n *html.Node) (words, images int) +{ /* ... */ } +``` + +bare return 按照返迴值列表的次序,返迴所有的返迴值,在上面的例子中,每一個return語句等價於: +``` +return words, images, err +``` +當一個函數有多處return語句以及許多返迴值時,bare return 可以減少代碼的重複,但是使得代碼難以被理解。舉個例子,如果你沒有仔細的審査代碼,很難發現前2處return等價於 return 0,0,err(Go會將返迴值 words和images在函數體的開始處,根據它們的類型,將其初始化爲0),最後一處return等價於 return words,image,nil。基於以上原因,不宜過度使用bare return。 + +練習 **5.5**: 實現countWordsAndImages。(參考練習4.9如何分詞) +練習 **5.6**: 脩改gopl.io/ch3/surface (§3.2) 中的corner函數,將返迴值命名,併使用bare return。