原文作者: 煎鱼 EDDYCJY
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
标准开场见多了,那内部标准库又是怎么输出这段英文的呢?今天一起来围观下源码吧 ?
func Print(a ...interface{}) (n int, err error) {
return Fprint(os.Stdout, a...)
}
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
以上三类就是最常见的格式化 I/O 的方法,我们将基于此去进行拆解描述
在这里我们使用 Print
方法做一个分析,便于后面的加深理解
func Print(a ...interface{}) (n int, err error) {
return Fprint(os.Stdout, a...)
}
Print
使用默认格式说明符打印格式并写入标准输出。另外当两者都为非空字符串时将插入一个空格
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrint(a)
n, err = w.Write(p.buf)
p.free()
return
}
该函数一共有两个形参:
1、 p := newPrinter(): 申请一个临时对象池(sync.Pool)
var ppFree = sync.Pool{
New: func() interface{} { return new(pp) },
}
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.fmt.init(&p.buf)
return p
}
2、 p.doPrint(a): 执行约定的格式化动作(参数间增加一个空格、最后一个参数增加换行符)
func (p *pp) doPrint(a []interface{}) {
prevString := false
for argNum, arg := range a {
true && false
isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String
// Add a space between two non-string arguments.
if argNum > 0 && !isString && !prevString {
p.buf.WriteByte(' ')
}
p.printArg(arg, 'v')
prevString = isString
}
}
可以看到底层通过判断该入参,同时满足以下条件就会添加分隔符(空格):
而在 Print
方法中,不需要指定格式符。实际上在该方法内直接指定为 v
。也就是默认格式的值
p.printArg(arg, 'v')
%v the value in a default format
when printing structs, the plus flag (%+v) adds field names
%#v a Go-syntax representation of the value
%T a Go-syntax representation of the type of the value
%% a literal percent sign; consumes no value
%t the word true or false
+ always print a sign for numeric values;
guarantee ASCII-only output for %q (%+q)
- pad with spaces on the right rather than the left (left-justify the field)
# alternate format: add leading 0 for octal (%#o), 0x for hex (%#x);
0X for hex (%#X); suppress 0x for %p (%#p);
for %q, print a raw (backquoted) string if strconv.CanBackquote
returns true;
always print a decimal point for %e, %E, %f, %F, %g and %G;
do not remove trailing zeros for %g and %G;
write e.g. U+0078 'x' if the character is printable for %U (%#U).
' ' (space) leave a space for elided sign in numbers (% d);
put spaces between bytes printing strings or slices in hex (% x, % X)
0 pad with leading zeros rather than spaces;
for numbers, this moves the padding after the sign
详细建议参见 Godoc
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
}
与 Print 相比,最大的不同就是 doPrintf 方法了。在这里我们来详细看看其代码,如下:
func (p *pp) doPrintf(format string, a []interface{}) {
end := len(format)
argNum := 0 // we process one argument per non-trivial format
afterIndex := false // previous item in format was an index like [3].
p.reordered = false
formatLoop:
for i := 0; i < end; {
p.goodArgNum = true
lasti := i
for i < end && format[i] != '%' {
i++
}
if i > lasti {
p.buf.WriteString(format[lasti:i])
}
if i >= end {
// done processing format string
break
}
// Process one verb
i++
// Do we have flags?
p.fmt.clearflags()
simpleFormat:
for ; i < end; i++ {
c := format[i]
switch c {
case '#': //'#'、'0'、'+'、'-'、' '
...
default:
if 'a' <= c && c <= 'z' && argNum < len(a) {
...
p.printArg(a[argNum], rune(c))
argNum++
i++
continue formatLoop
}
break simpleFormat
}
}
// Do we have an explicit argument index?
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
// Do we have width?
if i < end && format[i] == '*' {
...
}
// Do we have precision?
if i+1 < end && format[i] == '.' {
...
}
if !afterIndex {
argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a))
}
if i >= end {
p.buf.WriteString(noVerbString)
break
}
...
switch {
case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec.
p.buf.WriteByte('%')
case !p.goodArgNum:
p.badArgNum(verb)
case argNum >= len(a): // No argument left over to print for the current verb.
p.missingArg(verb)
case verb == 'v':
...
fallthrough
default:
p.printArg(a[argNum], verb)
argNum++
}
}
if !p.reordered && argNum < len(a) {
...
}
}
noVerbString
。值为 %!(NOVERB)在特殊情况下,若提供的参数集比 verb 标识符多。fmt 将会贪婪检查下去,将多出的参数集以特定的格式输出,如下:
fmt.Printf("%d", 1, 2, 3)
// 1%!(EXTRA int=2, int=3)
值得注意的是,当指定了参数索引或实际处理的参数小于入参的参数集时,就不会进行贪婪匹配来展示
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
}
在这个方法中,最大的区别就是 doPrintln,我们一起来看看,如下:
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')
}
%v
对参数进行格式化\n
字符在上例的执行流程分析中,可以看到格式化参数这一步是在 p.printArg(arg, verb)
执行的,我们一起来看看它都做了些什么?
func (p *pp) printArg(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}
if arg == nil {
switch verb {
case 'T', 'v':
p.fmt.padString(nilAngleString)
default:
p.badVerb(verb)
}
return
}
switch verb {
case 'T':
p.fmt.fmt_s(reflect.TypeOf(arg).String())
return
case 'p':
p.fmtPointer(reflect.ValueOf(arg), 'p')
return
}
// Some types can be done without reflection.
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
...
case reflect.Value:
if f.IsValid() && f.CanInterface() {
p.arg = f.Interface()
if p.handleMethods(verb) {
return
}
}
p.printValue(f, verb, 0)
default:
if !p.handleMethods(verb) {
p.printValue(reflect.ValueOf(f), verb, 0)
}
}
}
在小节代码中可以看见,fmt 本身对不同的类型做了不同的处理。这样子就避免了通过反射确定。相对的提高了性能
其中有两个特殊的方法,分别是 handleMethods
和 badVerb
,接下来分别来看看他们的作用是什么
1、badVerb
它主要用于格式化并处理错误的行为。我们可以一起来看看,代码如下:
func (p *pp) badVerb(verb rune) {
p.erroring = true
p.buf.WriteString(percentBangString)
p.buf.WriteRune(verb)
p.buf.WriteByte('(')
switch {
case p.arg != nil:
p.buf.WriteString(reflect.TypeOf(p.arg).String())
p.buf.WriteByte('=')
p.printArg(p.arg, 'v')
...
default:
p.buf.WriteString(nilAngleString)
}
p.buf.WriteByte(')')
p.erroring = false
}
在处理错误格式化时,我们可以对比以下例子:
fmt.Printf("%s", []int64{1, 2, 3})
// [%!s(int64=1) %!s(int64=2) %!s(int64=3)]%
在 badVerb 中可以看到错误字符串的处理主要分为以下部分:
2、handleMethods
func (p *pp) handleMethods(verb rune) (handled bool) {
if p.erroring {
return
}
// Is it a Formatter?
if formatter, ok := p.arg.(Formatter); ok {
handled = true
defer p.catchPanic(p.arg, verb)
formatter.Format(p, verb)
return
}
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
...
return false
}
这个方法比较特殊,一般在自定义结构体和未知情况下进行调用。主要流程是:
在 fmt 标准库中可以通过自定义结构体来实现方法的自定义,大致如下几种
type State interface {
Write(b []byte) (n int, err error)
Width() (wid int, ok bool)
Precision() (prec int, ok bool)
Flag(c int) bool
}
State 用于获取标志位的状态值,涉及如下:
type Formatter interface {
Format(f State, c rune)
}
Formatter 用于实现自定义格式化方法。可通过在自定义结构体中实现 Format 方法来实现这个目的
另外,可以通过 f 获取到当前标识符的宽度、精度等状态值。c 为 verb 标识符,可以得到其动作是什么
type Stringer interface {
String() string
}
当该对象为 String、Array、Slice 等类型时,将会调用 String()
方法对类字符串进行格式化
type GoStringer interface {
GoString() string
}
当格式化特定 verb 标识符(%v)时,将调用 GoString()
方法对其进行格式化
通过本文对 fmt 标准库的分析,可以发现它有以下特点:
总的来说,fmt 标准库有许多值得推敲的细节,希望你能够在本文学到
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。