用法
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. 处理完毕