3.5.6.为调试格式化
%T(类型)用于打印内置的或自定义值的类型,%v用于打印内置值的值。事实上,%v还可以打印自定义类型的值,对于没有定义String()方法的类型使用默认的格式,对于定义了String()方法的类型使用该类型的String()方法打印。
1 2 3 4 5 6 7 8 | p := polar{-83.40, 71.60} fmt.Printf("|%T|%v|%#v|\n", p, p, p) fmt.Printf("|%T|%v|%t|\n", false, false, false) fmt.Printf("|%T|%v|%d|\n", 7607, 7607, 7607) fmt.Printf("|%T|%v|%f|\n", math.E, math.E, math.E) fmt.Printf("|%T|%v|%f|\n", 5+7i, 5+7i, 5+7i) s := "Relativity" fmt.Printf("|%T|\"%v\"|\"%s\"|%q|\n", s, s, s, s) |
1 2 3 4 5 6 | |main.polar|{-83.4·71.6}|main.polar{radius:-83.4,·θ:71.6}| |bool|false|false| |int|7607|7607| |float64|2.718281828459045|2.718282| |complex128|(5+7i)|(5.000000+7.000000i)| |string|"Relativity"|"Relativity"|"Relativity"| |
上面这个例子演示了如何使用%t和%v来输出任意值的类型和值。如果类型满足%v的格式设置,那么我们可以简单地使用fmt.Print()或类似的函数来输出,因为这些函数默认使用%v格式。同时使用%v和“#”格式化动作修饰符只对结构体类型起作用并可以输出该结构休的类型名和成员名。对于浮点数,%v格式更像%g而不是%f。%T格式主要被用于调试且可以包含自定义类型的包名称(在本例中是main)。对于字符串,通常使用%q将该字符串以引号引起来以方便调试。
Go语言中有两种类型是同物异名的:byte和uint8,rune和int32。处理int不能处理(例读写二进制文件)的32位有符号整数时使用int32,处理Unicode码点时使用rune(字符)。
1 2 3 4 | s := "Alias↔Synonym" chars := []rune(s) bytes := []byte(s) fmt.Printf("%T: %v\n%T: %v\n", chars, chars, bytes, bytes) |
1 2 | []int32: [65 108 105 97 115 8596 83 121 110 111 110 121 109] []uint8: [65 108 105 97 115 226 134 148 83 121 110 111 110 121 109] |
如上面的代码所示,%T总是打印原始类型的名称,而不是其同物异名。因为字符串中有一个非 ASCII 字符,很显然我们创建了一个rune切片(码点)和一个UTF-8编码的字节切片。
Go语言也可以使用%p(指针)输出内存中任意值的地址。
1 2 3 4 5 | i := 5 f := -48.3124 3.5. String Formatting with the Fmt Package 105 s := "Tomás Bretón" fmt.Printf("|%p → %d|%p → %f|%#p → %s|\n", &i, i, &f, f, &s, s) |
1 | |0xf840000300·→·5|0xf840000308·→·-48.312400|f840001990·→·Tomás·Bretón| |
&地址操作符将在下一章介绍。如果我们使用%p格式化动作和#修饰符,就会丢弃地址开头处的0x。在调试时像这样输出内存地址是非常有用的。
Go语言输出切片和映射的功能对调试是非常有用的,就像输出通道的功能一样,也就是说,我们可以输出通过该通道发送和接收的类型和该通道的内存地址。
1 2 3 4 | fmt.Println([]float64{math.E, math.Pi, math.Phi}) fmt.Printf("%v\n", []float64{math.E, math.Pi, math.Phi}) fmt.Printf("%#v\n", []float64{math.E, math.Pi, math.Phi}) fmt.Printf("%.5f\n", []float64{math.E, math.Pi, math.Phi}) |
1 2 3 4 | [2.718281828459045·3.141592653589793·1.618033988749895] [2.718281828459045·3.141592653589793·1.618033988749895] []float64{2.718281828459045,·3.141592653589793,·1.618033988749895} [2.71828·3.14159·1.61803] |
使用未修饰的%v格式化动作,切片可以以方括号括起来并以空格分隔的形式输出。通常我们使用fmt.Print()或fmt.Sprint()这样的函数来输出切片,但如果我们需要使用格式化输出函数,通常我们使用%v 或%#v。然而,我们也可以使用类型兼容的格式化动作,如对于浮点数可以使用%f,对于字符串可以使用%s。
1 2 3 4 | fmt.Printf("%q\n", []string{"Software patents", "kill", "innovation"}) fmt.Printf("%v\n", []string{"Software patents", "kill", "innovation"}) fmt.Printf("%#v\n", []string{"Software patents", "kill", "innovation"}) fmt.Printf("%17s\n", []string{"Software patents", "kill", "innovation"}) |
1 2 3 4 | ["Software·patents"·"kill"·"innovation"] [Software·patents·kill·innovation] []string{"Software·patents",·"kill",·"innovation"} [·Software·patents··············kill········innovation] |
当字符串中含有空格时,使用%q输出字符串切片尤其有用,因为它使每个单独的字符串可以被识别出来,而%v则无此特性。
最后一个输出乍一看可能觉得是错误的,因为它占用了53个字符(不包括方括号)而不是51个(3个含有17个字符的字符串,都不太大)。这个明显的差异是由于输出了每个切片项之间的空格分隔符。
除了用于调试,当程序化的生成Go代码时,%#v也是非常有用的。
1 2 3 4 5 | fmt.Printf("%v\n", map[int]string{1: "A", 2: "B", 3: "C", 4: "D"}) fmt.Printf("%#v\n", map[int]string{1: "A", 2: "B", 3: "C", 4: "D"}) fmt.Printf("%v\n", map[int]int{1: 1, 2: 2, 3: 4, 4: 8}) fmt.Printf("%#v\n", map[int]int{1: 1, 2: 2, 3: 4, 4: 8}) fmt.Printf("%04b\n", map[int]int{1: 1, 2: 2, 3: 4, 4: 8}) |
1 2 3 4 5 | map[4:D·1:A·2:B·3:C] map[int]·string{4:"D",·1:"A",·2:"B",·3:"C"} map[4:8·1:1·2:2·3:4] map[int]·int{4:8,·1:1,·2:2,·3:4} map[0100:1000·0001:0001·0010:0010·0011:0100] |
映射在输出时会先输出map关键字,然后输出该映射的“键-值”对(任意顺序,因为映射是无序的)。就像切片一样,它也可以使用除%v以外的与该映射键值相兼容的的格式化动作,正如本例中最后一条语句使用的那样。
fmt包的打印函数用途很广且可以用于打印我们想要的任何输出。该fmt包唯一没有提供的函数是以某种特定的字符(而不是0或空格)进行填充,正如我们之前自定义的Pad()和Humanize()函数那样,这是很容易就可以做到的。