Go语言核心之美 4.3-多返回值

在Go语言中,函数可以有多个返回值,这个特性我们已经在之前的例子见过很多,很多标准库函数都会返回两个值,一个是期望得到的函数执行结果,另外一个是函数出错时的错误值。

下面的程序是findlinks的改进版本,它可以自己发起HTTP请求,这样就不必再运行fetch。因为HTTP请求和解析操作都可能会失败,因此findlinks声明了两个返回值:存储链接的列表和错误值。一般而言,HTML的解析器当遇到错误时,会构造出错误的HTML节点,因此解析HTML很少失败,一旦失败,很可能是因为IO错误导致的。

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将返回执行过程中遇到的错误信息,第一个直接返回错误信息,后面两个通过fmt.Errorf输出更加详细的错误信息。如果findlinks成功结束,最后的return语句将解析获得的链接列表返回给用户,这时的错误信息是nil。

在finallinks中,我们必须确保resp.Body被关闭,然后释放网络资源,甚至当发生错误时。虽然Go的垃圾回收机制会回收不再使用的内存,但是不要该假定未使用的操作系统资源也会被回收,例如打开的文件、网络连接等,我们应该进行显式的回收!

多返回值函数返回给调用者的是一组值,因此调用者必须显式的将这些值赋给相应的变量(如果这些变量会在后面被使用):

links, err := findLinks(url)

如果某个值不被使用,可以将其分配给空白操作符(blank identifier):

links, _ := findLinks(url) // 这里表示忽略返回的错误

一个函数可以将另一个有多返回值的函数作为返回值:

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语句以及许多返回值时,裸返回可以减少代码的重复,但是会让我们的代码难以理解。举个例子,如果你没有仔细的审查代码,很难发现前2处return等价于 return 0,0,err(函数的具名返回值会默认初始化为对应类型的零值),最后一处return等价于 return words,image,nil。基于以上原因,不宜过度使用裸返回。

练习 5.5: 实现countWordsAndImages。(参考练习4.9如何分词)

练习 5.6: 修改gopl.io/ch3/surface (§3.2) 中的corner函数,将返回值命名,并使用bare return。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值