多返回值
在 Go 中,一个函数可以返回多个值。我们已经在之前例子中看到,许多标准库中的函数返回2个值,一个是期望得到的返回值,另一个是函数出错时的错误信息。下面的例子会展示如何编写多返回值的函数。
下面的程序是 findlinks 的改进版本。修改后的 findlinks 可以自己发起 HTTP 请求,这样我们就不必再运行 fetch 。因为 HTTP 请求和解析操作可能会失败,因此 findlinks 声明了 2 个返回值:链接列表和错误信息。一般而言,HTML 的解析器可以处理 HTML 页面的错误结点,构造出 HTML 页面结构,所以解析 HTML 很少失败。这意味着如果 findlinks 函数失败了,很可能是由于 I/O 的错误导致的。
Unresolved include directive in modules/ROOT/pages/ch5/ch5-03.adoc - include::example$/ch5/findlinks2/main.go[]
在 findlinks 中,有 4 处 return 语句,每一处 return 都返回了一组值。前三处 return ,将 http 和 html 包中的错误信息传递给 findlinks 的调用者。第一处 return 直接返回错误信息,其他两处通过 fmt.Errorf(§7.8)输出详细的错误信息。如果 findlinks 成功结束,最后的 return 语句将一组解析获得的连接返回给用户。
在 findlinks 中,我们必须确保 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) { /* ... */ }
按照返回值列表的次序,返回所有的返回值,在上面的例子中,每一个 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 。