Go-知识-fmt
fmt 实现了格式化输出,并提供了相应的占位符。数值类型:整数类型,浮点类型字符类型指针类型布尔类型其他在java等一些语言中,输出调用自动从Object继承的ToString方法,将对象信息转为字符串。在go里面也有类似的接口Stringer接口定义的String接口就是默认的结构体转字符串的调用方法除此之外,还有一个 接口GoStringer定义的GoString用于适配%#v才会调用。_go fmt %
文章信息
- 原文链接:https://jiayq.blog.csdn.net/article/details/146027225
- 发布时间:2025-03-04 21:12:50
- 阅读量:1
- 标签:#golang, #go, #字符串, #格式化, #fmt
摘要
文章浏览阅读1k次,点赞17次,收藏11次。fmt 实现了格式化输出,并提供了相应的占位符。数值类型:整数类型,浮点类型字符类型指针类型布尔类型其他在java等一些语言中,输出调用自动从Object继承的ToString方法,将对象信息转为字符串。在go里面也有类似的接口Stringer接口定义的String接口就是默认的结构体转字符串的调用方法除此之外,还有一个 接口GoStringer定义的GoString用于适配%#v才会调用。_go fmt %
Go-知识-fmt
- 介绍
- 数值类型
- 字符类型
- 布尔类型
- 其他
- API
- Fprint
- Sprint
- Fprintf
- 格式穷举
- Printf
- Sprintf
- Fprintln
- Println
- Sprintln
- Fprint
- Appendln
- error
- 自定义
Go-知识-fmt
介绍
fmt 实现了格式化输出,并提供了相应的占位符。
支持的数据类型如下:
- 数值类型:整数类型,浮点类型
- 字符类型
- 指针类型
- 布尔类型
- 其他
数值类型
- %b : 二进制
- %o : 八进制
- %x : 十六进制
- %X : 十六进制
- %d : 十进制
- %f : 浮点类型
- %e : 科学计数法
- %E : 科学计数法
试一试
1
2
3
4
5
6
7
8
9
10
11
12
func TestFmt(t *testing.T) {
number := 100.234
numberInt := 45
fmt.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 : 带双引号
如下代码
1
2
3
4
5
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 := true fmt.Printf(“布尔类型%%b \t %t\n”, b) }
其他
- %T : 判断类型(输出类型)
- %p : 指针类型
- %v : 默认格式
%#v : 带语法的格式
func TestFmtOther(t *testing.T) { a := 1 b := 2.0 ok := true ptr := &a s := 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
源码如下:
1
2
3
4
5
6
7
8
//
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 干了啥
1
2
3
4
5
6
7
8
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
ppFree 又是个啥
1
2
3
var ppFree = sync.Pool{
New: func() any { return new(pp) },
}
ppFree 是一个缓存池
提高对象的利用率
p := ppFree.Get().(*pp)是从缓存池中拿一个 pp 结构,因为缓存池是 any 类型的,所以需要进行类型强转
Pp 结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type fmt struct {
buf *buffer
fmtFlags
wid int // width
prec 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 bool
precPresent bool
minus bool
plus bool
sharp bool
space bool
zero 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 bool
sharpV bool
}
对于 pp 结构中 fmt 的初始化
1
2
3
4
5
6
7
8
func (f *fmt) clearflags() {
f.fmtFlags = fmtFlags{}
}
func (f *fmt) init(buf *buffer) {
f.buf = buf
f.clearflags()
}
设置了缓存区,同时清空了标志位。因为 pp 的指针值是缓存的,拿出来的可能是之前用过的,所以需要先初始化清空一下才能使用。
` p := newPrinter()之后就是 p.doPrint(a) , 看下 doPrint`
1
2
3
4
5
6
7
8
9
10
11
12
func (p *pp) doPrint(a []any) {
prevString := false
for 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() 是把从缓存池中拿出来的对象还回去,并且刷新和释放缓存区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 = nil
p.value = reflect.Value{}
p.wrappedErrs = p.wrappedErrs[:0]
ppFree.Put(p)
}
Print 就是调用了 Fprint ,只是传入的writer是标准输出设备
1
2
3
func Print(a ...any) (n int, err error) {
return Fprint(os.Stdout, a...)
}
Sprint
Sprint 和 Fprint 差不多,Sprint不需要将缓存区里面的数据写入到 writer了,直接转为字符串返回即可
1
2
3
4
5
6
7
func Sprint(a ...any) string {
p := newPrinter()
p.doPrint(a)
s := string(p.buf)
p.free()
return s
}
Fprintf
Fprintf 相比 Fprint 多了一个入参,也就是格式串。格式串就是带有占位符的字符串
1
2
3
4
5
6
7
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
1
2
3
func Printf(format string, a ...any) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
Sprintf
Sprintf 不需要写入write,直接将缓存区的内容返回即可
1
2
3
4
5
6
7
func Sprintf(format string, a ...any) string {
p := newPrinter()
p.doPrintf(format, a)
s := string(p.buf)
p.free()
return s
}
Fprintln
Fprintln 基本上也大差不差的,核心是 doPrintln
1
2
3
4
5
6
7
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 ,都是使用默认格式打印的
1
2
3
4
5
6
7
8
9
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
1
2
3
func Println(a ...any) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
Sprintln
Sprintln 不需要写入writer 直接将缓冲区的内容返回即可
1
2
3
4
5
6
7
func Sprintln(a ...any) string {
p := newPrinter()
p.doPrintln(a)
s := string(p.buf)
p.free()
return s
}
Appendln
追加换行
1
2
3
4
5
6
7
func TestAppln(t *testing.T) {
var s []byte
s = 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接口就是默认的结构体转字符串的调用方法
1
2
3
4
5
6
7
8
9
10
11
12
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 才会调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
}



















