本文最初发表在我的个人博客,查看原文,获得更好的阅读体验
Go中的格式化打印使用类似C的printf
系列的风格,但功能更为丰富和通用。这些函数位于fmt
包中,并具有大写名称:fmt.Printf
,fmt.Fprintf
,fmt.Sprintf
等等。字符串函数(Sprintf
等)返回一个字符串,而不是填充提供的缓冲区。
fmt
包中打印函数如下:
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string
对于以f结尾的格式化打印函数Fprintf
,Printf
,Sprintf
可以接受一个格式字符串,其中格式字符串中有相应的占位符,不同的占位符格式效果不一样。
%v 默认格式。当打印结构类型时,带加号的标识(%+v)会添加字段名称
%#v 值的Go语法表示形式
%T 值类型的Go语法表示形式
%% 百分号标识字面量,无需提供值
示例:
package main
import (
"fmt"
)
func main() {
t := &T{7, -2.35, "abc\tdef"}
fmt.Printf("%v\n", t) // &{7 -2.35 abc def}
fmt.Printf("%+v\n", t) // &{a:7 b:-2.35 c:abc def}
fmt.Printf("%#v\n", t) // &main.T{a:7, b:-2.35, c:"abc\tdef"}
fmt.Printf("%T\n", t) // *main.T
fmt.Printf("20%%\n") // 20%
}
type T struct {
a int
b float64
c string
}
%t 输出布尔变量值 true 或 false
%b 二进制形式
%c 相应的Unicode码点表示的字符
%d 十进制形式
%o 八进制形式
%q 使用Go语法安全转义的单引号字符字面量
%x 十六进制形式,a-f为小写
%X 十六进制形式,A-F为大写
%U Unicode格式: U+1234; 等价于 "U+%04X"
示例:
a := 255
fmt.Printf("%b\n", a) // 11111111
fmt.Printf("%c\n", a) // ÿ
fmt.Printf("%d\n", a) // 255
fmt.Printf("%o\n", a) // 377
fmt.Printf("%q\n", a) // 'ÿ'
fmt.Printf("%x\n", a) // ff
fmt.Printf("%X\n", a) // FF
fmt.Printf("%U\n", a) // U+00FF
%b 指数为二的幂的无小数科学计数法,与strconv.FormatFloat函数的'b'格式一致。例如:6755399441055744p-52
%e 科学计数法(e小写), 如 -1.234456e+78
%E 科学计数法(E大写), 如 -1.234456E+78
%f 无指数小数,如 123.456
%F 同 %f
%g 用%e 表示大指数,否则用 %f 。
%G 用%E 表示大指数,否则用 %F 。
6755399441055744p-52 表示 6755399441055744 * 2-52,即:float64的1.5
关于精度的讨论,请参考下面1.8小节。
示例:
b := 1234567.891
fmt.Printf("%b\n", b) // 5302428716536693p-32
fmt.Printf("%e\n", b) // 1.234568e+06
fmt.Printf("%E\n", b) // 1.234568E+06
fmt.Printf("%f\n", b) // 1234567.891000
fmt.Printf("%F\n", b) // 1234567.891000
fmt.Printf("%g\n", b) // 1.234567891e+06
fmt.Printf("%G\n", b) // 1.234567891E+06
%s 字符串或字节切片的未解释字节。
%q Go语法安全转义的双引号字符串。
%x 十六进制形式,小写,每字节两个字符。
%X 十六进制形式,大写,每字节两个字符
示例:
s1 := `hello \nworld`
s2 := "hello \nworld"
fmt.Printf("%s\n", s1) // hello \nworld
fmt.Printf("%s\n", s2) // hello
// world // 注意这里换行了
fmt.Printf("%q\n", s1) // "hello \\nworld"
fmt.Printf("%q\n", s2) // "hello \nworld"
fmt.Printf("%x\n", s1) // 68656c6c6f205c6e776f726c64
fmt.Printf("%x\n", s2) // 68656c6c6f200a776f726c64
fmt.Printf("%X\n", s1) // 68656C6C6F205C6E776F726C64
fmt.Printf("%X\n", s2) // 68656C6C6F200A776F726C64
%p 第0个元素的地址的16进制表示形式,前导为0x。
示例:
s := make([]int, 2, 16)
fmt.Printf("%p\n", s) // 0x432080
%p 16进制表示形式,前导为0x。
%b, %d, %o, %x 和 %X 同样可以用来格式化指针,就像对待整数一样。
对于%v
表示的默认格式,不同类型相当于:
bool: %t
int, int8 等: %d
uint, uint8 等: %d, 如果是%#v,则为%#x
float32, complex64, 等: %g
string: %s
chan: %p
pointer: %p
对于复合对象,使用以下规则递归打印:
struct: {field0 field1 ...}
array, slice: [elem0 elem1 ...]
maps: map[key1:value1 key2:value2 ...]
以上类型的指针: &{}, &[], &map[]
另外,还可以指定宽度和精度,格式为%w.p占位符
,其中:w
为指定的宽度,.p
为指定的精度,注意p
前面的点号,并不是所有的占位符都可以使用精度。如果实际宽度小于指定的宽度,则在前面补空格;如果实际宽度大于指定宽度,以实际宽度为准;如果缺省,则默认宽度是表示该值所必需的宽度,对于小数部分,默认宽度总是6。如果没有点号.
,则使用默认精度;如果点号后没有数字p
,则精度为0
。精度在舍入时为四舍五入。宽度和精度均为十进制数字,且都是可选的。
示例:
b1 := 123456789.81234567
b2 := 12345678907.81
b3 := 12345678907.82
fmt.Printf("%f\n", b1) // "123456789.812346" - 默认宽度和精度
fmt.Printf("%f\n", b2) // "12345678907.809999" - 默认宽度和精度
fmt.Printf("%f\n", b3) // "12345678907.820000" - 默认宽度和精度
fmt.Println() //
fmt.Printf("%20f\n", b1) // " 123456789.812346" - 宽度20,默认精度
fmt.Printf("%.2f\n", b1) // "123456789.81" - 默认宽度,精度2
fmt.Printf("%16.2f\n", b1) // " 123456789.81" - 宽度16,精度2
fmt.Printf("%9.f\n", b1) // "123456790" - 宽度9,精度0
注意第6行的结果并不能精确表示实际值
另外,上述示例中的注释为了体现差异增加了两端的双引号,实际不会输出,下同
+ 总是为数字值打印符号;对于%q (%+q),只保证ASCII字符的输出。
- 右侧填充空格,而不是左侧(左对齐)
# 可选格式: 对于八进制(%#o)添加前导0;十六进制(%#x)添加前导0x,十六进制(%#X)添加前导0X;
对于%p (%#p)则抑制前导0x;
对于%q,如果strconv.CanBackquote返回true,则打印原始字符串(反引号字符串);
对于%e, %E, %f, %F, %g 和 %G总是打印一个小数点;
对于%g 和 %G,不会移除结尾的0;
如果字符'x'可用%U (%#U)打印,则会输出例如 U+0078 'x'
' ' (空格) 为数字中省略的符号保留一个空格(% d);
以十六进制打印字符串或切片时(% x, % X),在字节之间插入空格
0 使用0而不是空格填充宽度;对于数字,则是在符号后填充。
示例:
n1 := 123
b1 := 123456789.81234567
s1 := 'x'
s2 := "ABC"
fmt.Printf("%+d\n", n1) // "+123"
fmt.Printf("%+d\n", -n1) // "-123"
fmt.Println() //
fmt.Printf("%d\n", n1) // "123"
fmt.Printf("%d\n", -n1) // "-123"
fmt.Printf("% d\n", n1) // " 123"
fmt.Printf("% d\n", -n1) // "-123"
fmt.Println() //
fmt.Printf("%020f\n", -b1) // "-000123456789.812346"
fmt.Printf("%020f\n", b1) // "0000123456789.812346"
fmt.Printf("%#9.f\n", b1) // "123456790."
fmt.Println() //
fmt.Printf("%U\n", s1) // "U+0078"
fmt.Printf("%#U\n", s1) // "U+0078 'x'"
fmt.Printf("%5s\n", s2) // " ABC"
fmt.Printf("%-5s\n", s2) // "ABC "
fmt.Printf("%05s\n", s2) // "00ABC"
你不需要提供格式字符串。对于Printf
,Fprintf
和Sprintf
中的每一个,还有另外的函数与之对应,比如Print
和Println
,这些函数不使用格式字符串,而是为每个参数生成默认格式。Println
版本还在参数之间插入一个空格,并在输出中附加一个换行符,而Print
版本只在两边的操作数都不是字符串时才添加空格。下面的例子输出的结果一样:
fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))
格式化打印函数如fmt.Fprint
等,将任何实现了io.Writer
接口的对象作为第一个参数,像变量os.Stdout
和os.Stderr
。
接下来的事情与C不同,首先,像%d
等数字格式不接受符号或大小标识,相反,打印例程会使用参数的类型来决定这些属性。
var x uint64 = 1<<64 - 1
fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))
会打印出:
18446744073709551615 ffffffffffffffff; -1 -1
如果只是想要默认转换,例如decimal
转integer
,可以使用%v
格式,结果正是Print
和Println
将产生的结果。而且,该格式可以打印任何值,甚至是数组、切片,结构和映射。
例如打印映射的语句:
var color = map[string]string{
"red": "红",
"orange": "橙",
"yellow": "黄",
"green": "绿", // 注意每行结尾的逗号不能少
}
fmt.Printf("%v\n", color)
fmt.Println(color)
会打印出:
map[green:绿 orange:橙 red:红 yellow:黄]
map[green:绿 orange:橙 red:红 yellow:黄]
对于映射,输出的key的顺序是不固定的。对于结构类型,改版的%+v
格式使用它们的名称注解其中的字段,对于任何值,%#v
替换格式以完整的Go语法打印。
type T struct {
a int
b float64
c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
fmt.Printf("%#v\n", color)
会打印出:
&{7 -2.35 abc def}
&{a:7 b:-2.35 c:abc def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string]string{"green":"绿", "orange":"橙", "red":"红", "yellow":"黄"}
注意&
符号。当应用于string
或[]byte
类型的值时,带引号的字符串格式也可以通过%q
获得。如果可能,备用格式%#q
将使用反引号。(%q
也适用于整数和符文,会输出单引号的符号常量)。此外,%x
适用于字符串,字节数组和字节切片以及整数,生成长十六进制字符串,中间有个空格的% x
格式会在字节之间放置空格。
另一个方便的格式是%T
,它打印一个值的类型:
fmt.Printf("%T\n", color)
会打印出:
map[string]string
如果要控制自定义类型的默认格式,只需要在类型中定义一个String() string
签名的方法即可,例如:
func (t *T) String() string {
return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}
fmt.Printf("%v\n", t)
输出:
7/-2.35/"abc\tdef"
如果你需要像打印类型T
的指针那样打印T
的值,String
的接收器必须是一个值类型,这个例子使用了一个指针,因为对于结构类型更有效和惯用。
我们的String
方法能够调用Sprintf
,是因为打印例程完全是可重入的,并且以这种方式封装。但是,要理解这种方法有一个重要的细节:请勿通过调用Sprintf
来构造String
方法,因为它会无限递归String
方法。如果Sprintf
调用尝试直接以字符串形式打印接收器,而字符串又会再次调用该方法,就会发生这种情况,这是一个常见且容易犯的错误。例如以下的错误示例:
type MyString string
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.
}
很容易修复这个问题:将参数转换为基本字符串类型,该类型没有这个方法。
type MyString string
func (m MyString) String() string {
return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.
}
另一种解决办法是使用非字符串格式标记,例如:
type ByteSize float64
const (
_ = iota // ignore first value by assigning to blank identifier
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
func (b ByteSize) String() string {
switch {
case b >= YB:
return fmt.Sprintf("%.2fYB", b/YB)
case b >= ZB:
return fmt.Sprintf("%.2fZB", b/ZB)
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
在这里用Sprintf
实现ByteSize
的String
方法很安全(不会无限递归),这倒不是因为类型转换,而是它以%f
调用了Sprintf
,它并不是一种字符串格式:Sprintf
只会在它需要字符串时才调用String
方法,而%f
需要一个浮点数值。
另一种打印技术是将打印例程的参数直接传递给另一个这样的例程。Printf
的签名使用...interface{}
类型作为最终参数,以指定在格式化之后可以出现任意数量的参数(任意类型):
func Printf(format string, v ...interface{}) (n int, err error) {
在函数Printf
中,v
相当于[]interface
类型的变量,但是如果它被传递给另一个可变参数函数,它就像一个常规的参数列表。这是我们上面使用的函数的log.Println
的实现。它将其参数直接传递给fmt.Sprintln
以进行实际格式化。
// Println prints to the standard logger in the manner of fmt.Println.
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...)) // Output takes parameters (int, string)
}
我们在对嵌套的Sprintln
调用中的v
后边加上...
告诉编译器将v
视作参数列表,否则它只会将v
作为单个切片参数传递。
顺便说一下,...
参数可以是特定类型,例如用于选择最小整数列表min函数的... int
:
func Min(a ...int) int {
min := int(^uint(0) >> 1) // largest int
for _, i := range a {
if i < min {
min = i
}
}
return min
}
参考:
https://golang.org/doc/effective_go.html#printing
https://golang.org/pkg/fmt