gopl-zh.github.com/ch5/ch5-03.md
2016-01-21 09:58:28 +08:00

118 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 5.3. 多返迴值
在Go中一個函數可以返迴多個值。我們已經在之前例子中看到許多標準庫中的函數返迴2個值一個是期望得到的返迴值另一個是函數出錯時的錯誤信息。下面的例子會展示如何編寫多返迴值的函數。
下面的程序是findlinks的改進版本。脩改後的findlinks可以自己發起HTTP請求這樣我們就不必再運行fetch。因爲HTTP請求和解析操作可能會失敗因此findlinks聲明了2個返迴值鏈接列表和錯誤信息。一般而言HTML的解析器可以處理HTML頁面的錯誤結點構造出HTML頁面結構所以解析HTML很少失敗。這意味着如果findlinks函數失敗了很可能是由於I/O的錯誤導致的。
<u><i>gopl.io/ch5/findlinks2</i></u>
```Go
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的垃圾迴收機製會迴收不被使用的內存但是這不包括操作繫統層面的資源比如打開的文件、網絡連接。因此我們必須顯式的釋放這些資源。
調用多返迴值函數時,返迴給調用者的是一組值,調用者必須顯式的將這些值分配給變量:
```Go
links, err := findLinks(url)
```
如果某個值不被使用可以將其分配給blank identifier:
```Go
links, _ := findLinks(url) // errors ignored
```
一個函數內部可以將另一個有多返迴值的函數作爲返迴值下面的例子展示了與findLinks有相同功能的函數兩者的區别在於下面的例子先輸出參數
```Go
func findLinksLog(url string) ([]string, error) {
log.Printf("findLinks %s", url)
return findLinks(url)
}
```
當你調用接受多參數的函數時可以將一個返迴多參數的函數作爲該函數的參數。雖然這很少出現在實際生産代碼中但這個特性在debug時很方便我們隻需要一條語句就可以輸出所有的返迴值。下面的代碼是等價的
```Go
log.Println(findLinks(url))
links, err := findLinks(url)
log.Println(links, err)
```
準確的變量名可以傳達函數返迴值的含義。尤其在返迴值的類型都相同時,就像下面這樣:
```Go
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。
```Go
// 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) { /* ... */ }
```
按照返迴值列表的次序返迴所有的返迴值在上面的例子中每一個return語句等價於
```Go
return words, images, err
```
當一個函數有多處return語句以及許多返迴值時bare return 可以減少代碼的重複但是使得代碼難以被理解。舉個例子如果你沒有仔細的審査代碼很難發現前2處return等價於 return 0,0,errGo會將返迴值 words和images在函數體的開始處根據它們的類型將其初始化爲0最後一處return等價於 return wordsimagenil。基於以上原因不宜過度使用bare return。
**練習 5.5** 實現countWordsAndImages。參考練習4.9如何分詞)
**練習 5.6** 脩改gopl.io/ch3/surface (§3.2) 中的corner函數將返迴值命名併使用bare return。