从源码学习 Go 标准库(一):fmt - scan(5)& error

356 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情

前言

本系列文章将以源码和文档为依据,梳理标准库的内容,拓展对标准库的认识,并进一步探索标准库的使用方法。

第一章的主角是 fmt 包,它包括 format print scan errors 这四个部分,我们将按照这个顺序来依次分析。

上一篇文章中,我们已经将 scan.go 中所有的具体执行扫描操作的方法函数都介绍完了。本篇文章,我们会分析完 doScanf 和 Errorf,给整个 fmt 包做个收尾。

备注:本系列文章使用的是 go 1.19 源码:

github.com/golang/go/t…

结果注释中的 · 代表一个空格

scan.go

advance

github.com/golang/go/b…

advance 函数会判断到下一个参数之前,输入和格式化字符串是否匹配,并返回处理的字符个数。

循环以下流程:

  • 首先做空白符处理(下面所说的空白符不包括换行符)

    • format 中的换行符匹配输入中的零或多个空白符加上一个换行符或 EOF

    • format 中换行符之前的空白符会与换行符合并到一起,只当做一个换行符

    • format 中换行符之后的空白符与输入中的相应的换行符后的零或多个空白符匹配

    • 其他情况下,format 中的空白符匹配输入中的一个或多个空白符或者 EOF

    • 代码中先计算格式化字符串中到下一个非空白符之前,有几个换行符,以及是否以空白符结尾

    for isSpace(fmtc) && i < len(format) {
            if fmtc == '\n' {
                    newlines++
                    trailingSpace = false
            } else {
                    trailingSpace = true
            }
            i += w
            fmtc, w = utf8.DecodeRuneInString(format[i:])
    }
    
    • 然后处理输入,先根据第一条规则,将所有换行符处理完

    • 接着,如果结尾有空白符

      • 并且空白符后面没有跟着换行符,那么它至少需要匹配一个空白符或者EOF,而且不能匹配换行符

      • 然后匹配后面可能存在的多个空白符,直到换行符或者非空白符时停止

      • 如果最后一个字符不是EOF,把它退回

  • 然后处理 %

    • 如果 % 是格式化字符串最后一个字符,报错

    • 如果下一个字符不是 %,直接返回

    • 如果下一个字符也是 %,跳过第一个 %

  • 最后,处理文字是否匹配,如果不匹配会退回字符并返回 -1

doScanf

github.com/golang/go/b…

首先延迟调用错误处理函数,然后遍历格式化字符串,先通过 advance 判断输入和格式化字符串是否匹配,如果匹配正常(返回值大于0)则继续匹配;如果返回值小于等于0,说明遇到错误或参数:

  • 如果下一个字符不是 %,说明要么文字不匹配(-1),要么输入空了,报错

  • 然后跳过 %,并获取可能的宽度

  • 然后获取下一个格式化字符

    • 如果不是 c,要跳过空白符

    • 如果是 %,读取 %continue ,因为该操作不消耗参数

  • 如果处理的参数个数大于传入的参数个数,报错

  • 然后获取相应的传入参数,并调用 scanOne 赋值,处理的参数个数加一

循环结束后,如果处理的参数个数小于传入的参数个数,报错

error.go

github.com/golang/go/b…

error.go 只有一个导出函数就是 Errorf,用于格式化错误信息。当我们能够确定错误类型时,可能更常使用 errors.New() 来创建错误,但当错误内容不确定时,就需要通过 Errorf 来创建错误。

Errorf 有一个独特的格式化动词 %w,它只能使用一次,用于把错误和错误信息封装在一起。 结构如下:

type wrapError struct {
	msg string
	err error
}

它有两个方法,分别返回它的两个字段值:

func (e *wrapError) Error() string {
	return e.msg
}

func (e *wrapError) Unwrap() error {
	return e.err
}

如果没有 %w 直接用 errors.New 创建错误并返回;如果有 %w,则封装为 wrapError 再返回。

if p.wrappedErr == nil {
        err = errors.New(s)
} else {
        err = &wrapError{s, p.wrappedErr}
}

Errorf 用法如下:

func fn() error {
	keys := []int{1, 2, 4}
	mp := map[int]bool{1: true, 2: true, 3: true}
	for _, key := range keys {
		if v, ok := mp[key]; ok {
			Print(v, " ")
		} else {
			err := Errorf("invalid key:%d", key)
			return err
		}
	}
	return nil
}
func main() {
	err := fn()
	Println(err) // true true invalid keys:4
}

也可以把 Errorf() 里面改为 "%w:%d", errors.New("invalid keys"), key

然后使用 Println(errors.Unwrap(err)) 就可以打印出 invalid keys

总结

至此,我们已经将整个 fmt 标准库都学习完了。通过对源码的阅读,我们不仅更深入地了解了各种函数的使用方法和细节,也在优秀代码的熏陶下提升了自己的视野与代码水平。

最后,如果本篇文章对您有所帮助,希望您可以 点赞、收藏、评论,感谢支持 ✧(≖ ◡ ≖✿