ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 4.12\. 打印输出 前面例子中涉及到的打印都比较简单。在这一节中,我们将要讨论Go语言格式化输出的功能。 我们已经用过"fmt"包中的"Printf"和"Fprintf"等输出函数。"fmt"包中的"Printf"函数的 完整说明如下: ``` Printf(format string, v ...) (n int, errno os.Error) ``` 其中"..."表示数目可变参数,和C语言中"stdarg.h"中的宏类似。不过Go中,可变参数是通道 一个空接口("interface {}")和反射(reflection)库实现的。反射特性可以帮助"Printf" 函数很好的获取参数的详细特征。 在C语言中,printf函数的要格式化的参数类型必须和格式化字符串中的标志一致。不过在Go语言中, 这些细节都被简化了。我们不再需要"%llud"之类的标志,只用"%d"表示要输出一个整数。至于对应 参数的实际类型,"Printf"可以通过反射获取。例如: ``` 10 var u64 uint64 = 1<<64-1 11 fmt.Printf("%d %d\n", u64, int64(u64)) ``` 输出 ``` 18446744073709551615 -1 ``` 最简单的方法是用"%v"标志,它可以以适当的格式输出任意的类型(包括数组和结构)。下面的程序, ``` 14 type T struct { 15 a int 16 b string 17 } 18 t := T{77, "Sunset Strip"} 19 a := []int{1, 2, 3, 4} 20 fmt.Printf("%v %v %v\n", u64, t, a) ``` 将输出: ``` 18446744073709551615 {77 Sunset Strip} [1 2 3 4] ``` 如果是使用"Print"或"Println"函数的话,甚至不需要格式化字符串。这些函数会针对数据类型 自动作转换。"Print"函数默认将每个参数以"%v"格式输出,"Println"函数则是在"Print"函数 的输出基础上增加一个换行。一下两种输出方式和前面的输出结果是一致的。 ``` 21 fmt.Print(u64, " ", t, " ", a, "\n") 22 fmt.Println(u64, t, a) ``` 如果要用"Printf"或"Print"函数输出似有的结构类型,之需要为该结构实现一个"String()"方法, 返回相应的字符串就可以了。打印函数会先检测该类型是否实现了"String()"方法,如果实现了则以 该方法返回字符串作为输出。下面是一个简单的例子。 ``` 09 type testType struct { 10 a int 11 b string 12 } 14 func (t *testType) String() string { 15 return fmt.Sprint(t.a) + " " + t.b 16 } 18 func main() { 19 t := &testType{77, "Sunset Strip"} 20 fmt.Println(t) 21 } ``` 因为 *testType 类型有 String() 方法,因此格式化函数用它作为输出结果: ``` 77 Sunset Strip ``` 前面的例子中,"String()"方法用到了"Sprint"(从字面意思可以猜测函数将返回一个字符串) 作为格式化的基础函数。在Go中,我们可以递归使用"fmt"库中的函数来为格式化服务。 "Printf"函数的另一种输出是"%T"格式,它输出的内容更加详细,可以作为调试信息用。 自己实现一个功能完备,可以输出各种格式和精度的函数是可能的。不过这不是该教程的重点,大家 可以把它当作一个课后练习。 读者可能有疑问,"Printf"函数是如何知道变量是否有"String()"函数实现的。实际上,我们 需要先将变量转换为Stringer接口类型,如果转换成功则表示有"String()"方法。下面是一个 演示的例子: ``` type Stringer interface { String() string } s, ok := v.(Stringer); // Test whether v implements "String()" if ok { result = s.String() } else { result = defaultOutput(v) } ``` 这里用到了类型断言("v.(Stringer)"),用来判断变量"v"是否可以满足"Stringer"接口。 如果满足,"s"将对应转换后的Stringer接口类型并且"ok"被设置为"true"。然后我们通过"s", 以Stringer接口的方式调用String()函数。如果不满足该接口特征,"ok"将被设置为false。 "Stringer"接口的命名通常是在接口方法的名字后面加e?r后缀,这里是"String+er"。 Go中的打印函数,除了"Printf"和"Sprintf"等之外,还有一个"Fprintf"函数。不过"Fprintf"函数和 的第一个参数并不是一个文件,而是一个在"io"库中定义的接口类型: ``` type Writer interface { Write(p []byte) (n int, err os.Error); } ``` 这里的接口也是采用类似的命名习惯,类型的接口还有"io.Reader"和"io.ReadWriter?"等。 在调用"Fprintf"函数时,可以用实现了"Write"方法的任意类型变量作为参数,例如文件、网络、管道等等。