fmt
模板
fmt中的转义词
-
%v
通用占位符,该占位符会自动转换变量为string(以默认选项)。通常用于转换基础类型
-
%#v
会将变量转换为符合go syntax的输出,也就是说我们可以直接复制输出结果然后粘贴到代码中而不会报错。我们可以看一个例子来加深理解
func main() { c := struct { hello string world string }{"1", "2"} fmt.Printf("%#v", c) } // 输出结果 // struct { hello string; world string }{hello:"1", world:"2"}
-
%T
打印变量类型。通常用于DEBUG检查实际类型
-
%d
打印十进制下的整数,也可以使用%v但是%d会意图更加明确
-
%x或者%X
打印十六进制下的整数。一个有用的做法是传入字节切片,会输出两位数的十六进制数字
-
%f
打印没有e的浮点数。也可以使用%v但是%f允许我们设置宽度以及精确度等
-
%q
打印quoted字符串。常常用于当数据中存在不可见字符
-
%p
打印变量的指针地址。常常用于debug的时候检查是否不同的指针变量指向相同数据
宽度和精度
我们可以通过增加很多flags来控制转义词的输出效果。尤其是对于浮点数来说,通常我们需要对其精度和宽度做限制
-
设置精度
我们可以通过在%后面添加一个.以及数字来设置精度,比方说
%.2f
会将传入的100.567
输出为100.57
(注意这里使用四舍五入) -
设置宽度
我们可以通过一个紧跟在%后面的数字来指定宽度,如果变量长度小于指定宽度,那么会用空格填充。因此这个功能可以用于打印类似表格的效果。比方说
%8.2f
会将传入的100.567
打印为••100.57
(•表示空格)
左对齐
输出默认的是右对齐,意味着空格(如果需要的话)会填充在左边。如果我们需要左对齐,可以在%后面添加-
,比方说%-8.2f
会将传入的100.567
打印为100.57••
(•表示空格)
用零填充
有些时候我们可能希望用0来填充而不是空格,比方说我们希望生成定长整数字符串。我们可以通过在%后紧跟0来实现这点。比方说%08d
会将传入的123
打印为00000123
其他转义词和flag
可以参考fmt官方文档中的Printing部分
输出
fmt主要用于格式化字符串,这些格式化函数基于输出类型来分组:STDOUT, io.Writer以及string
每一组都有三个函数:默认格式化,用户定义格式化以及默认格式化+换行
输出至STDOUT
最常用到的就是通过输出到STDOUT在终端上显示,可以使用Print
函数组来实现这个目的
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)
期中,Print
函数仅仅会基于默认格式打印一系列变量至STDOUT;而Printf
函数允许我们通过上面提到的模板进行格式化;Println
函数类似于Print但是会在变量之间插入空格以及在最后加上换行符
通常我们会使用Printf
来格式化输出,如果需要默认格式会使用Println
因为一般我们需要换行。一个例外是当我们试图与用户交互的时候,如果我们希望输出What is your name?
后光标在输出字符串后面而不是换行,我们可以使用Printf
输出至io.Writer
如果我们希望打印至非STDOUT的io.Writer(比方说STDERR或者buffer),那么我们可以使用Fprint
函数组。其中F表示FILE,这是继承自C语言的fprintf
函数
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)
这些函数类似于Print
函数组只是需要我们定义writer,事实上Print
函数组仅仅是对Fprint
函数组的简单封装
我们可以用Fprintf
来抽象出STDOUT
从而方便我们实现单元测试(我们可以传入buffer然后验证输出结果)
输出至字符串
有些时候我们就希望得到字符串,一个办法是使用buffer+Fprintf函数,但是可能较为繁琐。我们可以直接使用Sprintf
函数组
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string
值得注意的是,Sprintf
虽然较为方便,但是如果常常生成字符串,往往会变成性能瓶颈。如果在性能剖析之后发现需要优化,可以重用bytes.Buffer
+Fprintf
来提高速度
错误格式化
我们可以使用Errorf
来格式化错误,函数定义如下
func Errorf(format string, a ...interface{}) error
该函数其实是对errors.New
以及Sprintf
的简单封装
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
扫描(scanning)
fmt也提供了对应的函数从我们格式化的输入中提取数据写入变量,这个过程称之为扫描(scanning)。和输出类似,对应函数可以基于 从STDIN读取、从io.Reader
读取以及从字符串中读取分组
不过注意的是,通常来说,我们通过CLI flags、环境变量或者API调用来获取输入值而不是通过扫描这种方式
从STDIN中读取
和Print写入STDOUT相似,我们可以使用Scan函数组来读取STDIN
func Scan(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)
Scan函数会基于空格来划分输入然后分别写入变量中(换行符被视为空格)
Scanf函数允许我们使用格式化字符串来识别格式化选项
Scanln函数类似于Scan函数但是不会将换行符视为空格
下面是一个相关的例子
var name string
var age int
if _, err := fmt.Scan(&name, &age); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("Your name is: %s\n", name)
fmt.Printf("Your age is: %d\n", age)
//输出结果
//Jane 25 --这是输入
//Your name is: Jane
//Your age is: 25
从io.Reader中读取
同样可以用Fscan
函数组来读取reader
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
从字符串中读取
func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
Stringer接口
对于%s以及调用了Print
函数的变量来说,go会检查是否实现了Stringer接口,如果是则调用String函数
type Stringer interface {
String() string
}
格式化至go中
我们也可以实现GoStringer
接口来修改%#v
的显示结果,不过一般%#v
已经能够输出满意的结果所以比较少用
type GoStringer interface {
GoString() string
}
用户定义类型
fmt中最让人疑惑部分就是用户自定义输出和写入。它们可以让你完全控制对象如何被输出(在使用Printf和Scanf的时候)
格式化器(Formatters)
可以通过实现Formatter
接口来为自己的类型提供格式化器,接口定义如下
type Formatter interface {
Format(f State, c rune)
}
其中State对象列举了指定的所有标记(flag)包括宽度、精度等,c rune识别了转义词的字符(即%s
中的s或者%v
中的v)
-
一个简单的使用例子
下面的结构体会基于宽度在最前面打印若干个#,基于精度在后面打印若干个☃
// Header represents formattable header text. type Header string // Format decorates the header with pounds and snowmen. func (hdr Header) Format(f fmt.State, c rune) { wid, _ := f.Width() prec, _ := f.Precision() f.Write([]byte(strings.Repeat("#", wid))) f.Write([]byte(hdr)) f.Write([]byte(strings.Repeat("☃", prec))) } hdr := Header(“GO WALKTHROUGH”) fmt.Printf(“%2.3s\n”, hdr) //输出结果 //##GO WALKTHROUGH☃☃☃
在go标准库中,格式化器常常用于特别的数字类型如big.Float
或者big.Int
读取器(Scanners)
对应Fomartter
我们也有相应的Scanner
接口
type Scanner interface {
Scan(state ScanState, verb rune) error
}
总结
-
如果不需要任何格式化选项,使用
%v
-
尽量使用宽度和精度,特别是对于浮点数
-
尽量不要使用
Scan
函数用于读取,通常有更好的选择 -
如果默认实现不可行,尽量定义
String
函数 -
不要使用自定义格式化器和读取器,通常没有太好的用例
一个比较有意义的用例适用于实现字节数向MB的格式转换,例子
-
如果发现fmt是性能瓶颈,可以转为使用strconv,但是要先做性能剖析