go fmt分析

用法

    package main
    import "fmt"

    fmt.Printf("%d %s", 1, "123")
    fmt.Println(123, "123")

中文文档

 # 通用:
%v	值的默认格式表示
%+v	类似%v,但输出结构体时会添加字段名
%#v	值的Go语法表示
%T	值的类型的Go语法表示
%%	百分号

// 布尔值
%t	单词true或false

# 整数:
%b    表示为二进制
%c    该值对应的unicode码值
%d    表示为十进制
%o    表示为八进制
%q    该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x    表示为十六进制,使用a-f
%X    表示为十六进制,使用A-F
%U    表示为Unicode格式:U+1234,等价于"U+%04X"

# 浮点数与复数的两个组分:
%b	无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
%e	科学计数法,如-1234.456e+78
%E	科学计数法,如-1234.456E+78
%f	有小数部分但无指数部分,如123.456
%F	等价于%f
%g	根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G	根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

# 字符串和[]byte:
%s	直接输出字符串或者[]byte
%q	该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x	每个字节用两字符十六进制数表示(使用a-f)
%X	每个字节用两字符十六进制数表示(使用A-F)

# 指针:
%p	表示为十六进制,并加上前导的0x    

没有%u。整数如果是无符号类型自然输出也是无符号的。类似的,也没有必要指定操作数的尺寸(int8,int64)。

宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。
精度通过(可选的)宽度后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。举例如下:
%f:    默认宽度,默认精度
%9f    宽度9,默认精度
%.2f   默认宽度,精度2
%9.2f  宽度9,精度2
%9.f   宽度9,精度0   

结构

常用的函数Println调用Fprintln,Printf调用Fprintf。

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
	p := newPrinter()
	p.doPrintf(format, a)
	n, err = w.Write(p.buf)
	p.free()
	return
}

func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
	p := newPrinter()
	p.doPrintln(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}

从部分源码中可以看出,需要先构建pp一个结构体,结构体中包含一个 buf来存需要标准输出的内容。

pp结构体

   # pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.
type pp struct {
	buf buffer
	// arg holds the current item, as an interface{}.
	arg interface{}
	// value is used instead of arg for reflect values.
	value reflect.Value
	// fmt is used to format basic items such as integers or strings.
	fmt fmt
	// reordered records whether the format string used argument reordering.
	reordered bool
	// goodArgNum records whether the most recent reordering directive was valid.
	goodArgNum bool
	// panicking is set by catchPanic to avoid infinite panic, recover, panic, ... recursion.
	panicking bool
	// erroring is set when printing an error string to guard against calling handleMethods.
	erroring bool
	// wrapErrs is set when the format string may contain a %w verb.
	wrapErrs bool
	// wrappedErr records the target of the %w verb.
	wrappedErr error
}

继续往下主要处理的函数是 func doPrintln() 相对来说没那么麻烦直接上源码:

// doPrintln is like doPrint but always adds a space between arguments
// and a newline after the last argument.
func (p *pp) doPrintln(a []interface{}) {
	for argNum, arg := range a {
		if argNum > 0 {
			p.buf.writeByte(' ')
		}
		p.printArg(arg, 'v')
	}
	p.buf.writeByte('\n')
}
更细节部分:
    p.printArg(arg, verb)
    fmt 本身对不同的类型做了不同的处理。这样子就避免了通过反射确定。
    相对的提高了性能,其中有两个特殊的方法,分别是 handleMethods 和 badVerb。
    1. badVerb它主要用于格式化并处理错误的行为。
        在 badVerb 中可以看到错误字符串的处理主要分为以下部分:
            约定前缀错误标志:%!
            当前的格式化操作符
            约定格式符:(
            当前参数的类型
            约定格式符:=
            当前参数的值(默认以 %v 格式化)
            约定格式符:)

    2. handleMethods
        这个方法比较特殊,一般在自定义结构体和未知情况下进行调用。主要流程是:
            若当前参数为错误 verb 标识符,则直接返回
            判断是否实现了 Formatter
            实现,则利用自定义 Formatter 格式化参数
            未实现,则最大程度的利用 Go syntax 默认规则去格式化参数    

处理doPrintf() 就比较复杂了:

func (p *pp) doPrintf(format string, a []interface{})
  首先需要对format进行字符遍历。
    1. 写入 % 之前的字符内容
    2. 如果所有标志位处理完毕(到达字符尾部),则跳出处理逻辑
    3. (往后移)跳过 % ,开始处理其他 verb 标志位
    4. 清空(重新初始化) fmt 配置
    5. 处理一些基础的 verb 标识符(simpleFormat)。如:'#'、'0'、'+'、'-'、' ' 以及简单的 verbs 标识符(不包含精度、宽度和参数索引)。
        若当前字符为简单 verb 标识符。则直接进行处理。完成后会直接后移到下一个字符。其余标志位则变更 fmt 配置项,便于后续处理。
    6. 处理参数索引(argument index)
    7. 处理参数宽度(width)
    8. 处理参数精度(precision)
    9. % 之后若不存在 verbs 标识符则返回 noVerbString。值为 %!(NOVERB)
    10. 处理特殊 verbs 标识符(如:'%%'、'%#v'、'%+v')、错误情况(如:参数索引指定错误、参数集个数与 verbs 标识符数量不匹配)或进行格式化参数集
    11. 处理完毕
    

fmt相关博客

猜你喜欢

转载自blog.csdn.net/weixin_56766616/article/details/129955862