探索Go语言中fmt包的输入输出功能

发表时间: 2024-05-21 17:33

通用:

%v 值的默认格式表示。当输出结构体时, 扩展标志(%+v)会添加字段名

%#v 值的Go语法表示

%T 值的类型的Go语法表示

%% 百分号

布尔值:

%t 单词true或false

整数:

%b 表示为二进制

%c 该值对应的unicode码值

%d 表示为十进制

%o 表示为八进制

%q 该值对应的单引号括起来的go语法字符字面值, 必要时会采用安全的转义表示

%x 表示为十六进制, 使用a-f

%X 表示为十六进制, 使用A-F

%U 表示为Unicode格式:U+1234, 等价于"U+%04X"

浮点数、复数的两个组分:

%b 无小数部分、二进制指数的科学计数法

%e 科学计数法,如-1234.456e+78

%E 科学计数法,如-1234.456E+78

%f 有小数部分但无指数部分, 如123.456

%F 等价于%f

%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)

%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

字符串和[]byte:

%s 直接输出字符串或者[]byte

%q 该值对应的双引号括起来的go语法字符串字面值, 必要时会采用安全的转义表示

%x 每个字节用两字符十六进制数表示(使用a-f)

%X 每个字节用两字符十六进制数表示(使用A-F)

指针:

%p 表示为十六进制, 并加上前导的0x

%f: 默认宽度, 默认精度

%9f 宽度9, 默认精度

%.2f 默认宽度, 精度2

%9.2f 宽度9, 精度2

%9.f 宽度9, 精度0


实例:

package mainimport "fmt"import "os"type point struct {x, y int}func main() {    //Go 为常规 Go 值的格式化设计提供了多种打印方式。例如,这里打印了 point 结构体的一个实例。    p := point{1, 2}    fmt.Printf("%v\n", p) // {1 2}    //如果值是一个结构体,%+v 的格式化输出内容将包括结构体的字段名。    fmt.Printf("%+v\n", p) // {x:1 y:2}    //%#v 形式则输出这个值的 Go 语法表示。例如,值的运行源代码片段。    fmt.Printf("%#v\n", p) // main.point{x:1, y:2}    //需要打印值的类型,使用 %T。    fmt.Printf("%T\n", p) // main.point    //格式化布尔值是简单的。    fmt.Printf("%t\n", true) // true    //格式化整形数有多种方式,使用 %d进行标准的十进制格式化。    fmt.Printf("%d\n", 123) // 123    //这个输出二进制表示形式。    fmt.Printf("%b\n", 14) // 1110    //这个输出给定整数的对应字符。    fmt.Printf("%c\n", 33) // !    //%x 提供十六进制编码。    fmt.Printf("%x\n", 456) // 1c8    //对于浮点型同样有很多的格式化选项。使用 %f 进行最基本的十进制格式化。    fmt.Printf("%f\n", 78.9) // 78.900000    //%e 和 %E 将浮点型格式化为(稍微有一点不同的)科学技科学记数法表示形式。    fmt.Printf("%e\n", 123400000.0) // 1.234000e+08    fmt.Printf("%E\n", 123400000.0) // 1.234000E+08    //使用 %s 进行基本的字符串输出。    fmt.Printf("%s\n", "\"string\"") // "string"    //像 Go 源代码中那样带有双引号的输出,使用 %q。    fmt.Printf("%q\n", "\"string\"") // "\"string\""    //和上面的整形数一样,%x 输出使用 base-16 编码的字符串,每个字节使用 2 个字符表示。    fmt.Printf("%x\n", "hex this") // 6865782074686973    //要输出一个指针的值,使用 %p。    fmt.Printf("%p\n", &p) // 0xc00004a070    //当输出数字的时候,你将经常想要控制输出结果的宽度和精度,可以使用在 % 后面使用数字来控制输出宽度。默认结果使用右对齐并且通过空格来填充空白部分。    fmt.Printf("|%6d|%6d|\n", 12, 345) // | 12| 345|    //你也可以指定浮点型的输出宽度,同时也可以通过 宽度.精度 的语法来指定输出的精度。    fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45) // | 1.20| 3.45|    //要最对齐,使用 - 标志。    fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45) // |1.20 |3.45 |    //你也许也想控制字符串输出时的宽度,特别是要确保他们在类表格输出时的对齐。这是基本的右对齐宽度表示。    fmt.Printf("|%6s|%6s|\n", "foo", "b") // | foo| b|    //要左对齐,和数字一样,使用 - 标志。    fmt.Printf("|%-6s|%-6s|\n", "foo", "b") // |foo |b |    //到目前为止,我们已经看过 Printf了,它通过 os.Stdout输出格式化的字符串。Sprintf 则格式化并返回一个字符串而不带任何输出。    s := fmt.Sprintf("a %s", "string")    fmt.Println(s) // a string    //你可以使用 Fprintf 来格式化并输出到 io.Writers而不是 os.Stdout。    fmt.Fprintf(os.Stderr, "an %s\n", "error") // an error}

实例: 切片如何打印

package mainimport ("fmt")type point struct {x, y int}func main() {var list []string = []string{"xuchenkai", "pengjihuan", "zhanghangyi"}fmt.Printf("%s\n", list) //使用%s [xuchenkai pengjihuan zhanghangyi]var visitor []int = []int{10001, 10002, 10003}fmt.Printf("%d\n", visitor) //使用%d [10001 10002 10003]fmt.Printf("%+v\n", list) //%+v 的格式化输出内容将包括结构体的字段名 []string{"xuchenkai", "pengjihuan", "zhanghangyi"}fmt.Printf("%#v\n", visitor) //%#v 形式则输出这个值的 Go 语法表示 []int{10001, 10002, 10003}p := point{1, 2}fmt.Printf("%v\n", p) //%v 值的默认格式表示 {1 2}fmt.Printf("%+v\n", p) //%+v 的格式化输出内容将包括结构体的字段名 {x:1 y:2}fmt.Printf("%#v\n", p) //%#v 形式则输出这个值的 Go 语法表示 main.point{x:1, y:2}}/*[xuchenkai pengjihuan zhanghangyi][10001 10002 10003][xuchenkai pengjihuan zhanghangyi][]int{10001, 10002, 10003}{1 2}{x:1 y:2}main.point{x:1, y:2}*/

关于使用log与使用fmt的区别

最初的就是直接打印出来, 之后一点点升级, 比如加上输出的时间, 加上goroutine之间的并发操作(打印信息并不能一定按照你规定好的顺序输出来 每次输出的顺序可能会不同), 按照不同的日志级别进行打印 。。。

加上打印以及转存, 因此就需要专门的log包来记录程序的信息。

想象一下, 一个很大的项目有很多的输出信息, 要是输出信息前面没有加上日期的话, debug起来几乎是难以想象的, 因为不可能一直盯着屏幕来对信息进行监控。

因此采用log来代替fmt的原因主要有一下几个方面:

添加了输出时间

线程安全

方便对日志信息进行转存,形成日志文件

fmt包String(),Error(),Format(),GoString()的接口实现

golang的接口使用非常广泛, 几乎每一个包都会用到接口, fmt包的使用率最多之一。

在实际开发中, 要定义结构体的标准输出用String(), 定义标准错误输出Error(), 定义格式化输出Format(), 还有比较特殊的GoString()。接下来描述接口的使用方式, 使用场景, 还有注意的地方。

String()

package mainimport ("fmt")type TestString struct{}func (t TestString) String() string {    return "我是String"}func main() {    fmt.Println(TestString{}) //我是String}

使用起来比较简单, 只要结构体里面有String() string就可以输出。

fmt包里面会判断有没有fmt.Stringer的接口, 然后再调用。

通常用于结构体的默认输出, 例如:

package mainimport ("fmt")type Student struct {    number int    realname string    age int}func main() {    stu := &Student{        number: 1,        realname: "王小明",        age: 18,    }    fmt.Println(stu) // &{1 王小明 18}}

改成:

package mainimport ("fmt")type Student struct {    number int    realname string    age int}func (t *Student) String() string {		return fmt.Sprintf("学号: %d\n真实姓名: %s\n年龄: %d\n", t.number, t.realname, t.age)}func main() {    stu := &Student{    number: 1,    realname: "王小明",    age: 18,    }    fmt.Println(stu)}/*学号: 1真实姓名: 王小明年龄: 18*/

Error

type TestError struct {}func (t TestError) Error() string {    return "我是Error"}func main() {    fmt.Println(TestString{})}// 我是Error


实际上使用方式跟String()一样, 但是设计代码时不能互相替换实现。

最常用的用法是独立封装type XXXError struct{}, 在文章最尾会揣摸一下为什么要这样用。

Format

type TestFormat struct {}func (t TestFormat) Format(s fmt.State, c rune) {    switch c {    case 'c':    switch {    case s.Flag('+'):    fmt.Printf("我是+c\n")    default:    fmt.Fprint(s, "我是c\n")    }    default:    fmt.Print("我是Format")    }}func main() {    t := TestFormat{}    fmt.Println(t)    fmt.Printf("%c\n", t)    fmt.Printf("%+c\n", t)    fmt.Printf("%s\n", t)}

我是Format

我是c

我是+c

我是Format

fmt.Println也会调用Format的接口, 所以String() Format()不能同一个结构体里面。

通常使用跟Error()类似, 可以参考一下github.com/pkg/errors里的stack.go的func (f Frame) Format(s fmt.State, verb rune)

GoString

type TestGoString struct {}func (t TestGoString) GoString() string {    return "我是GoString"}func main() {    t := TestGoString{}    fmt.Println(TestGoString{})    fmt.Printf("%s %#v\n", t, t)}

{}

{} 我是GoString

如上所示fmt.Println并没调用GoString方法, 只能通过格式化%#+标记输出。

在没有实现接口的情况下, 通常用来输出默认相应值, 如下:

func main() {var i uint = 18// 输出十六进制fmt.Printf("%x\n", i)fmt.Printf("%#x\n", i)}120x12func (p *pp) handleMethods(verb rune) (handled bool) {...// 判断Formatterif formatter, ok := p.arg.(Formatter); ok {...formatter.Format(p, verb)return}// 判断是否含有#标识符if p.fmt.sharpV {// 判断GoStrinerif stringer, ok := p.arg.(GoStringer); ok {...p.fmt.fmtS(stringer.GoString())return}} else {switch verb {case 'v', 's', 'x', 'X', 'q':switch v := p.arg.(type) {// 符合error接口case error:...p.fmtString(v.Error(), verb)return// 符合Stringer接口case Stringer:...p.fmtString(v.String(), verb)return}}}return false}

Format -> (#)GoString -> ((v,s,x,X,q)Error -> String) 源码四个接口都在handlerMethods方法调用控制, 都不是互相独立, 根据优先顺序调用。所以接口的设计, 尽可能独立封装, 避免混淆。

小结

String() 用于对结构体的标准输出等。

Error() 封装error的方法, 可以改一些错误上传到日志系统或者打印Stack。

Format() 对于String()的高级用法, 用于多种类型或者格式使用。

GoString() 常用于相对值。

注意事项

fmt/print.go的pp.handleMethods(verb rune) (handled bool)