呼玛网站建设在线资源搜索引擎
Go-知识-fmt
- 介绍
- 数值类型
- 字符类型
- 布尔类型
- 其他
- API
- Fprint
- Sprint
- Fprintf
- 格式穷举
- Printf
- Sprintf
- Fprintln
- Println
- Sprintln
- Appendln
- error
- 自定义
Go-知识-fmt
介绍
fmt 实现了格式化输出,并提供了相应的占位符。
支持的数据类型如下:
- 数值类型:整数类型,浮点类型
- 字符类型
- 指针类型
- 布尔类型
- 其他
数值类型
- %b : 二进制
- %o : 八进制
- %x : 十六进制
- %X : 十六进制
- %d : 十进制
- %f : 浮点类型
- %e : 科学计数法
- %E : 科学计数法
试一试
func TestFmt(t *testing.T) {number := 100.234numberInt := 45fmt.Printf("整数%%d \t %d\n", numberInt)fmt.Printf("八进制%%o \t %o\n", numberInt)fmt.Printf("十六进制%%x \t %x\n", numberInt)fmt.Printf("十六进制%%X \t %X\n", numberInt)fmt.Printf("布尔值%%b \t %b\n", numberInt)fmt.Printf("浮点值%%f \t %f\n", number)fmt.Printf("科学计数法%%e \t %e\n", number)fmt.Printf("科学计数法%%E \t %E\n", number)
}
执行结果如下
字符类型
- %s : 字符类型
- %q : 带双引号
如下代码
func TestFmtString(t *testing.T) {str := "hello world"fmt.Printf("字符类型%%s \t %s\n", str)fmt.Printf("待双引号%%q \t %q\n", str)
}
执行结果如下
布尔类型
- %t : 布尔类型
func TestFmtBool(t *testing.T) {b := truefmt.Printf("布尔类型%%b \t %t\n", b)
}
其他
- %T : 判断类型(输出类型)
- %p : 指针类型
- %v : 默认格式
- %#v : 带语法的格式
func TestFmtOther(t *testing.T) {a := 1b := 2.0ok := trueptr := &as := struct {Name string}{Name: "test",}fmt.Printf("类型%%T \t %T\n", a)fmt.Printf("类型%%T \t %T\n", b)fmt.Printf("类型%%T \t %T\n", ok)fmt.Printf("类型%%T \t %T\n", ptr)fmt.Printf("类型%%T \t %T\n", s)fmt.Println()fmt.Printf("指针%%p \t %p\n", ptr)fmt.Printf("指针%%p \t %p\n", &a)fmt.Printf("默认格式%%v \t %v\n", s)fmt.Printf("带语法格式%%#v \t %#v\n", s)
}
API
Fprint/Fprintf/Fprintln
: 带格式的输出Print/Printf/Println
: 标准输出Sprint/Sprintf/Sprintln
: 格式化内容为 string
Fprint/Print/Sprint
表示使用默认的格式输出或者格式化内容,Fprintf/Printf/Sprintf
表示使用指定的格式输出或格式化内容,Fprintln/Println/Sprintln
表示使用默认的格式输出或格式化内容,同时会在最后加上换行符\n
Fprint
源码如下:
//
func Fprint(w io.Writer, a ...any) (n int, err error) {p := newPrinter()p.doPrint(a)n, err = w.Write(p.buf)p.free()return
}
newPrinter
干了啥
func newPrinter() *pp {p := ppFree.Get().(*pp)p.panicking = falsep.erroring = falsep.wrapErrs = falsep.fmt.init(&p.buf)return p
}
ppFree
又是个啥
var ppFree = sync.Pool{New: func() any { return new(pp) },
}
ppFree 是一个缓存池
提高对象的利用率
p := ppFree.Get().(*pp)
是从缓存池中拿一个 pp 结构,因为缓存池是 any 类型的,所以需要进行类型强转
Pp 结构如下:
type pp struct {buf buffer// arg holds the current item, as an interface{}.arg any// 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// wrappedErrs records the targets of the %w verb.wrappedErrs []int
}
有很多的属性
p.fmt.init(&p.buf)
设置pp结构的 fmt 的属性
其中的 fmt 结构
type fmt struct {buf *bufferfmtFlagswid int // widthprec int // precision// intbuf is large enough to store %b of an int64 with a sign and// avoids padding at the end of the struct on 32 bit architectures.intbuf [68]byte
}type fmtFlags struct {widPresent boolprecPresent boolminus boolplus boolsharp boolspace boolzero bool// For the formats %+v %#v, we set the plusV/sharpV flags// and clear the plus/sharp flags since %+v and %#v are in effect// different, flagless formats set at the top level.plusV boolsharpV bool
}
对于 pp 结构中 fmt 的初始化
func (f *fmt) clearflags() {f.fmtFlags = fmtFlags{}
}func (f *fmt) init(buf *buffer) {f.buf = buff.clearflags()
}
设置了缓存区,同时清空了标志位。因为 pp 的指针值是缓存的,拿出来的可能是之前用过的,所以需要先初始化清空一下才能使用。
p := newPrinter()
之后就是 p.doPrint(a)
, 看下 doPrint
func (p *pp) doPrint(a []any) {prevString := falsefor argNum, arg := range a {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}
}
先判断是不是字符串,如果不是字符串,那么在每个值中间加一个 空格
接着调用 printArg
打印
printArg
就是针对go支持的所有格式化占位符,进行替换的一个过程
等待占位符替换完成后,将缓存区内的数据,写入到传入的writer
里,n, err = w.Write(p.buf)
最后一步的 p.free()
是把从缓存池中拿出来的对象还回去,并且刷新和释放缓存区
func (p *pp) free() {// Proper usage of a sync.Pool requires each entry to have approximately// the same memory cost. To obtain this property when the stored type// contains a variably-sized buffer, we add a hard limit on the maximum// buffer to place back in the pool. If the buffer is larger than the// limit, we drop the buffer and recycle just the printer.//// See https://golang.org/issue/23199.if cap(p.buf) > 64*1024 {p.buf = nil} else {p.buf = p.buf[:0]}if cap(p.wrappedErrs) > 8 {p.wrappedErrs = nil}p.arg = nilp.value = reflect.Value{}p.wrappedErrs = p.wrappedErrs[:0]ppFree.Put(p)
}
Print
就是调用了 Fprint
,只是传入的writer
是标准输出设备
func Print(a ...any) (n int, err error) {return Fprint(os.Stdout, a...)
}
Sprint
Sprint
和 Fprint
差不多,Sprint
不需要将缓存区里面的数据写入到 writer
了,直接转为字符串返回即可
func Sprint(a ...any) string {p := newPrinter()p.doPrint(a)s := string(p.buf)p.free()return s
}
Fprintf
Fprintf
相比 Fprint
多了一个入参,也就是格式串。格式串就是带有占位符的字符串
func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {p := newPrinter()p.doPrintf(format, a)n, err = w.Write(p.buf)p.free()return
}
其主要逻辑与 Fprint
相同,区别在于 doPrintf
通过 for i := 0; i < end; 循环逐字符解析格式字符串,分为两个处理阶段:
- 普通字符:直接写入缓冲区
- 格式化指令:以 % 开头的部分
遇到 % 后执行以下步骤:
- 解析标志(#, 0, +, -, )
- 处理参数索引(如 %[3]d)
- 解析宽度和精度(支持 * 动态值)
- 处理特殊动词:
case ‘w’:
p.wrappedErrs = append(p.wrappedErrs, argNum)
case ‘v’:
// 处理 Go 语法格式
参数处理
- 使用 argNum 跟踪当前参数索引
- 调用 printArg 进行实际格式化操作
- 支持参数重排序(%[n] 语法)
- 错误包装:处理 %w 时记录错误参数位置
- 类型反射:通过 reflect.Value 处理不同类型
- 性能优化:使用 buffer 结构进行高效字符串拼接
- 语法兼容:支持完整的 printf 语法规范
格式穷举
格式串 | 输出示例 | 说明 |
---|---|---|
%v | 42 | 通用格式,自动匹配类型 |
%+v | {Name: ""} | 带字段名的结构体输出 |
%#v | "go" | Go语法表示值(带类型信息) |
%T | float64 | 输出值的类型 |
%d | 255 | 十进制整数 |
%b | 101 | 二进制表示 |
%o | 10 | 八进制表示 |
%x | f | 小写十六进制 |
%X | F | 大写十六进制 |
%c | A | Unicode字符 |
%f | 3.141500 | 默认精度浮点数 |
%.2f | 3.14 | 保留2位小数 |
%e | 1.234500e+03 | 科学计数法表示 |
%g | 1.23456789e+08 | 自动选择最紧凑表示法 |
%s | hello | 原始字符串输出 |
%q | "go" | 带双引号的字符串 |
%x | 676f | 字符串的十六进制编码 |
%5d | 42 | 右对齐宽度5 |
%-5d | 42 | 左对齐宽度5 |
%05d | 00042 | 零填充宽度5 |
%+d | +42 | 显示正负号 |
% d | 42 | 正数前留空格 |
%[2]d %[1]d | 2 1 | 参数索引重排序 |
%*d | 42 | 动态宽度(参数指定宽度5) |
%.*f | 3.14 | 动态精度(参数指定精度2) |
%p | 0xc0000160a8 | 指针地址 |
%w | error | 错误包装(需配合errors包使用) |
%v | [1 2] | 切片/数组的默认输出 |
%#v | []int{1, 2} | 切片/数组的Go语法表示 |
%+#10.3f | +3.142 | 组合格式:符号+宽度10+精度3 |
%[3]d %[1]s | 42 hello | 多参数混合索引 |
%+v | {X:1 Y:2} | 结构体带字段名输出 |
%U | U+0041 | Unicode码点格式 |
%#b | 0b101 | Go语法二进制表示 |
%#o | 0o10 | Go语法八进制表示 |
%#x | 0xf | Go语法十六进制表示 |
%s | MyInt | 自定义类型实现String()方法时的输出 |
%d | %!d(string=text) | 类型不匹配时的错误提示 |
%d | %!d(MISSING) | 缺少参数时的错误提示 |
Printf
Printf
很简单直接用标准输出调用 Fprintf
func Printf(format string, a ...any) (n int, err error) {return Fprintf(os.Stdout, format, a...)
}
Sprintf
Sprintf
不需要写入write
,直接将缓存区的内容返回即可
func Sprintf(format string, a ...any) string {p := newPrinter()p.doPrintf(format, a)s := string(p.buf)p.free()return s
}
Fprintln
Fprintln
基本上也大差不差的,核心是 doPrintln
func Fprintln(w io.Writer, a ...any) (n int, err error) {p := newPrinter()p.doPrintln(a)n, err = w.Write(p.buf)p.free()return
}
doPrintln
在 doPrint
的基础上,在最后加了换行符\n
,都是使用默认格式打印的
func (p *pp) doPrintln(a []any) {for argNum, arg := range a {if argNum > 0 {p.buf.writeByte(' ')}p.printArg(arg, 'v')}p.buf.writeByte('\n')
}
Println
Println
将标准输出作为 writer
请求 Fprintln
func Println(a ...any) (n int, err error) {return Fprintln(os.Stdout, a...)
}
Sprintln
Sprintln
不需要写入writer
直接将缓冲区的内容返回即可
func Sprintln(a ...any) string {p := newPrinter()p.doPrintln(a)s := string(p.buf)p.free()return s
}
Appendln
追加换行
func TestAppln(t *testing.T) {var s []bytes = fmt.Appendln(s, "hello world")s = fmt.Appendln(s, "hello world")s = fmt.Appendln(s, "hello world")fmt.Print(string(s))
}
error
在格式串中 %w
是错误类型的格式串
除此之外,fmt有针对错误的函数Errorf
在 doPrintf
里面会统计 %w
的信息
如果只有一个 %w
,那么直接使用 errors.New
,如果有多个,需要进行warp处理
errors.New
实际上就是 stringError
warpErrors
就是多个错误
自定义
在java等一些语言中,输出调用自动从 Object
继承的ToString
方法,将对象信息转为字符串。
在go里面也有类似的接口
Stringer
接口定义的String
接口就是默认的结构体转字符串的调用方法
type T struct {Name string
}func (t T) String() string {return fmt.Sprintln(fmt.Sprintf("%q", t.Name))
}func TestString(t *testing.T) {name := T{"hello world"}fmt.Print(name)
}
除此之外,还有一个 接口GoStringer
定义的 GoString
用于适配 %#v
才会调用
type T struct {Name string
}func (t T) String() string {return fmt.Sprintln(fmt.Sprintf("%q", t.Name))
}func (t T) GoString() string {return fmt.Sprintln(fmt.Sprintf("struct T : %q", t.Name))
}func TestString(t *testing.T) {name := T{"hello world"}fmt.Print(name)fmt.Printf("%#v", name)
}