扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
功能:fmt包实现了类似C语言printf和scanf的格式化I/O。格式化动作('verb')源自C语言但更简单。
10年积累的成都网站设计、网站制作经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先做网站后付款的网站建设流程,更有大方免费网站建设让你可以放心的选择与我们合作。
Printf 函数有超过10个各种转义字符,Go 程序员称为 verb。下表不完整,但是它说明了很多可用的功能:
verb | 描述 |
---|---|
%d | 十进制数 |
%x, %o, %b | 十六进制,八进制,二进制数 |
%f, %g, %e | 浮点数 |
%t | 布尔型 |
%c | 字符(Unicode码点) |
%s | 字符串 |
%q | 带引号字符串或者字符 |
%v | 内置格式的任何值 |
%T | 任何值的类型 |
%% | 百分号本身 |
以ln结尾的,比如fmt.Println,使用%v的方式来格式化参数,并且在最后追加换行符。
上面的表格比较概括,而且也不全,对于不同的类型,还有不同的细节。所有的说明都在fmt包的doc.go文档里有详细的说明:http://docscn.studygolang.com/src/fmt/doc.go
普通占位符:
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%v | 相应值的默认格式。 | Printf("%v", people) | {zhangsan} |
%+v | 打印结构体时,会添加字段名 | Printf("%+v", people) | {Name:zhangsan} |
%#v | 相应值的Go语法表示 | Printf("%#v", people) | main.Human{Name:"zhangsan"} |
%T | 相应值的类型的Go语法表示 | Printf("%T", people) | main.Human |
%% | 字面上的百分号,并非值的占位符 | Printf("%%") | % |
布尔占位符:
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%t | true 或 false。 | Printf("%t", true) | true |
整数占位符:
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 二进制表示 | Printf("%b", 5) | 101 |
%c | 相应Unicode码点所表示的字符 | Printf("%c", 0x4E2D) | 中 |
%d | 十进制表示 | Printf("%d", 0x12) | 18 |
%o | 八进制表示 | Printf("%d", 10) | 12 |
%q | 单引号围绕的字符字面值,由Go语法安全地转义 | Printf("%q", 0x4E2D) | '中' |
%x | 十六进制表示,字母形式为小写 a-f | Printf("%x", 13) | d |
%X | 十六进制表示,字母形式为大写 A-F | Printf("%x", 13) | D |
%U | Unicode格式,相当于 "%04X" 加上前导 "U+" | Printf("%U", 0x4E2D) | U+4E2D |
浮点数和复数:
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%b | 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat 的 'b' 转换格式一致。例如 -123456p-78 | ||
%e | 科学计数法,例如 -1234.456e+78 | Printf("%e", 10.2) | 1.020000e+01 |
%E | 科学计数法,例如 -1234.456E+78 | Printf("%e", 10.2) | 1.020000E+01 |
%f | 有小数点而无指数,例如 123.456 | Printf("%f", 10.2) | 10.200000 |
%g | 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 | Printf("%g", 10.20) | 10.2 |
%G | 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 | Printf("%G", 10.20+2i) | (10.2+2i) |
指针:
占位符 | 说明 | 举例 | 输出 |
---|---|---|---|
%p | 十六进制表示,前缀 0x | Printf("%p", &people) | 0x4f57f0 |
其他标记(副词):
fmt.Printf("%q %+[1]q\n", "中文") // "中文" "\u4e2d\u6587"
副词#的备用格式:
八进制、十六进制,默认没有前导,使用#后会添加前导符号"0"、"0x"、"0X",防止产生歧义:
fmt.Printf("%o %#[1]o\n", 123) // 173 0173
fmt.Printf("%x %#[1]x %#[1]X\n", 123) // 7b 0x7b 0X7B
指针默认有前导,备用格式就是就掉前导:
var s string
fmt.Printf("%p %#[1]p\n", &s) // 0xc00004c240 c00004c240
对于字符串,%#q有些情况下会输出反引号围绕的字符串,不过测试下来不总是这样:
fmt.Printf("%q, %#[1]q\n", "ab\tcd") // "ab\tcd", `ab cd`
fmt.Printf("%q, %#[1]q\n", "ab\ncd") // "ab\ncd", "ab\ncd"
对于Unicode,打印出字符的编码后还会打印该字符:
fmt.Printf("%U, %#[1]U", '中') // U+4E2D, U+4E2D '中'
下面是一些可以通过合理的构造格式化字符串来打到最佳的显示效果的示例。
可能有些示例之间会有点重复。还有一些示例因为太简单,感觉上一节已经讲过了。不过上一节的介绍偏理论,而这里的内容更注重实际使用。
一、%后的副词[1]告知Printf重复使用第一个操作数。
二、%o、%x、%X之前的副词#告知Printf输出相应的前缀 0、0x、0X。
func main() {
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // 438 666 0666
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x) // 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
}
使用%x可以输出字节序列的UTF-8编码,还可以加上空格也就是% x,这样每个字符还能隔开,在对字节序列进行输出的时候特别有用:
fmt.Printf("%x\n", "abcdefg") // "61626364656667"
fmt.Printf("% x\n", "abcdefg") // "61 62 63 64 65 66 67"
fmt.Printf("% #x\n", "abcdefg") // "0x61 0x62 0x63 0x64 0x65 0x66 0x67"
对于Unicode字符,还是按照字节处理的,如若要输出Unicode码点,需要先转成[]rune类型:
fmt.Printf("% x\n", "世界") // "e4 b8 96 e7 95 8c"
fmt.Printf("%x\n", []rune("世界")) // "[4e16 754c]"
通过指定相同的宽度,可以做到右对齐的效果:
func main() {
fmt.Printf("%4d\n", 1)
fmt.Printf("%4d\n", 10)
fmt.Printf("%4d\n", 100)
fmt.Printf("%4d\n", 1000)
fmt.Printf("%4d\n", 10000) // 这个会超出宽度
}
/* 输出结果
$ go run main.go
1
10
100
1000
10000
*/
这里的页面似乎看不出对齐的效果,可能是空格的宽度不太一样。下面使用0填充的对齐效果就有了
如果宽度不够,输出时也不会丢失信息,而是把信息全部输出,不受宽度的限制。
默认使用空格填充,也可以指定填充的内容,比如使用0填充,在输出二进制数的时候非常有用:
func main() {
fmt.Printf("%04b\n", 1)
fmt.Printf("%04b\n", 2)
fmt.Printf("%04b\n", 3)
fmt.Printf("%04b\n", 4)
fmt.Printf("%04b\n", 666) // 这个会超出宽度
}
/* 输出结果
$ go run main.go
0001
0010
0011
0100
1010011010
*/
操作数字的时候,宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。
对于 %g 和 %G 精度是所有数字的总和,而用 %f 打印出来同样是小数,精度是小数点后面的位数。比如:123.45,%.4g 是 "123.5" 而 %.2f 是 "123.45"。
const n float64 = 123.45
fmt.Printf("%.4g %.2[1]f", n) // g:123.5 f:123.45
打印结构体的时候,使用副词#或者+可以使结构化符号%v以类似Go语法的方式输出对象,这个方法里面包含了成员变量的名字:
package main
import "fmt"
// 这可以表示一个坐标
type Point struct {
X, Y int
}
// 坐标加上半径就是一个圆
type Circle struct {
Point
Radius int
}
// 圆加上辐条数,这表示一个轮子
type Wheel struct {
Circle
Spokes int
}
var w Wheel
func main() {
w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
Circle: Circle{
Point: Point{X: 8, Y: 8},
Radius: 5,
},
Spokes: 20,
}
fmt.Printf("%v\n", w)
fmt.Printf("%#v\n", w)
}
/* 执行结果
PS H:\Go\src\gopl\ch5\embed> go run main.go
{{{8 8} 5} 20}
main.Wheel{Circle:main.Circle{Point:main.Point{X:8, Y:8}, Radius:5}, Spokes:20}
PS H:\Go\src\gopl\ch5\embed>
*/
宽度和精度对于字符串输出同样有效:
这样就可以在输出字符串的时候做到左对齐和右对齐,输出类似表格的样式:
package main
import "fmt"
type message struct {
Title string
text string
}
func main() {
list := []message{
{"fmt", "Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler. "},
{"bytes", "Package bytes implements functions for the manipulation of byte slices. It is analogous to the facilities of the strings package. "},
{"time", "Package time provides functionality for measuring and displaying time. "},
{"net/http", "Package http provides HTTP client and server implementations. "},
}
_ = list
for _, msg := range list {
fmt.Printf("%9.9s %.99s\n", msg.Title, msg.text)
}
}
/* 输出效果
PS H:\Go\src\gopl\output> go run main.go
fmt Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format '
bytes Package bytes implements functions for the manipulation of byte slices. It is analogous to the faci
time Package time provides functionality for measuring and displaying time.
net/http Package http provides HTTP client and server implementations.
PS H:\Go\src\gopl\output>
*/
不过对于中文输出就不会那么漂亮了,因为这里填充的空格是半角的空格。
左对齐
其实只要用上制表符\t就能对齐了,不过如果字符串的长度相差比较大,也无法对齐,并且间隔是自动的。下面是另一种精确的实现方法。
默认是在前面进行填充,似乎也没有在后面填充的格式。不过可以用下面的方法输出左对齐的效果:
fmt.Printf("%s%*s %.99s\n", msg.Title, 10-len(msg.Title), "", msg.text)
这里的%*s
是在s对应的内容输出前,动态的填充由变量指定的前导空格。由于这里s对应的是空字符串,所以就相当于是动态添加空格,而空格的数量是可以根据前一个变量的长度计算后动态变化的。这是下一节的内容,不过这里正好先展示下效果。
还有一种控制宽度的方法,使用*号,这种方式输出的宽度是由之后的变量决定的所以是可变的:
func main() {
for i := 0; i < 3; i++ {
fmt.Printf("%*d\n", i*4, i)
}
}
/* 执行结果
PS H:\Go\src\gopl\output> go run main.go
0
1
2
PS H:\Go\src\gopl\output> go run main.go
*/
输出的宽度由第一个变量控制,而输出的内容是第二个变量。
这里加了个0,这样就用0来代替原来的空格来填补宽度了,下面显示的效果更加直观:
func main() {
for i := 0; i < 3; i++ {
fmt.Printf("%0*d\n", i*4, i)
}
}
/* 执行结果
PS H:\Go\src\gopl\output> go run main.go
0
0001
00000002
PS H:\Go\src\gopl\output>
*/
由于宽度是包含字符串本身的,而缩进效果是不包括字符串的。所以可是使用空字符串作为第二个参数,把要输出的内容放在后面。
推荐这么用,fmt.Printf("%*s<%s>\n", n*2, "", "div")
第一个变量控制宽度,第二个是输出的字符串,这里是空字符串,这里就是两格缩进的效果。把要输出的内容放在后面,这里的第三个变量。
这种显示效果在处理代码和html标签的时候特别好,下面的程序先通过Get请求获取一个页面,然后解析页面中的文档树,并输出有缩进效果的树结构:
package main
import (
"fmt"
"net/http"
"os"
"golang.org/x/net/html"
)
func main() {
for _, url := range os.Args[1:] {
outline(url)
}
}
func outline(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
return err
}
forEachNode(doc, startElement, endElement)
return nil
}
// 调用 pre(x) 和 post(x) 遍历以n为根的树中的每个节点x
// 两个函数都是可选的
// pre 在子节点被访问前调用(前序调用)
// post 在访问后调用(后续调用)
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
var depth int
func startElement(n *html.Node) {
if n.Type == html.ElementNode {
fmt.Printf("%*s<%s>\n", depth*2, "", n.Data)
depth++
}
}
func endElement(n *html.Node) {
if n.Type == html.ElementNode {
depth--
fmt.Printf("%*s%s>\n", depth*2, "", n.Data)
}
}
/* 执行结果
PS H:\Go\src\gopl\ch6\outline2> go run main.go http://baidu.com
PS H:\Go\src\gopl\ch6\outline2>
*/
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流