本文最初發表在我的個人博客,查看原文,獲得更好的閱讀體驗
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
可以接受一個格式字符串,其中格式字符串中有相應的佔位符,不同的佔位符格式效果不一樣。
1.1 常規格式化
%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
}
1.2 布爾格式化
%t 輸出布爾變量值 true 或 false
1.3 整數格式化
%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
1.4 浮點數和複數格式化
%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
1.5 字符串和字節切片格式化
%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
1.6 切片格式化
%p 第0個元素的地址的16進製表示形式,前導爲0x。
示例:
s := make([]int, 2, 16)
fmt.Printf("%p\n", s) // 0x432080
1.7 指針格式化
%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[]
1.8 寬度和精度
另外,還可以指定寬度和精度,格式爲%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行的結果並不能精確表示實際值
另外,上述示例中的註釋爲了體現差異增加了兩端的雙引號,實際不會輸出,下同
1.9 其他標記
+ 總是爲數字值打印符號;對於%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