目录

格式化字符串

由于字符串通常是有书面文本组成的,因此在许多情况下,我们可能希望更好地控制字符串的外观,以便通过标点,换行和缩进使字符串对人类更具可读性。对于一些特殊的使用场景,我们希望保存在程序中的变量值插入到字符串中,这就需要格式化字符串。Go对字符串格式化提供了良好的支持,在 Go 语言中,fmt.Sprintf,fmt.Printf,fmt.Fprintf, Log.Printf, log.Panicf 等函数常常会用到格式化字符串参数。格式化字符串本质上与普通的字符串无异,它是由普通的字符串加上格式化样式和参数组成的。下面我们就来谈谈Go语言的格式化样式。

格式化样式

格式化样式是一种字符串形式,格式化字符以%开头,加上一个表示格式化字符类型的字符串组成了格式化样式。例如, %s表示字符串格式,%d表示整数格式。详细的格式化样式表总结如下:

格式化样式描述
%s输出字符串(string和[]byte)值
%d输出十进制整数值
%b输出整型的二进制表示形式
%#b输出整型的二进制完整表示形式
%o输出整型的八进制表示形式
%#o输出整型的八进制完整表示形式
%x输出整型的十六进制表示形式
%#x输出整型的十六进制完整表示形式
%X输出整型的十六进制表示形式(大写)
%#X输出整型的十六进制完整表示形式(大写)
%v输出值的本来形式
%+v输出值的本来形式,如果值为结构体,对结构体字段名和值展开
%#v输出Go语法格式的值
%t输出布尔值
%T输出值对应的Go语言类型
%%输出百分号(%)
%c输出Unicode码对应的字符
%U将Unicode码转换为对应的Unicode码点
%f输出浮点数
%e输出数值的科学计数法形式
%E与%e功能相同
%g输出紧凑的浮点数
%p输出指针
%q格式化字符串,在字符串的两端加上双引号

%s

使用格式

%[-][length]s
-可选,表示左对齐
length可选,一个大于0的整数,表示格式化宽度

格式化字符串

%s输出字符串。%s可以将string和与string具有相同底层类型的类型输出。

package main

import "fmt"

func main() {
	sentence := "I love Go."
	fmt.Printf("He say: '%s'\n", sentence)
	// output: He say: 'I love Go.'

	type S string
	fmt.Printf("He say: '%s'\n", S(sentence))
	// output: He say: 'I love Go.'
}

格式化字节数组

%s也可以输出字节切片[]byte和字节数组[...]byte。

package main

import "fmt"

func main() {
	list := []byte{97, 98, 99}
	fmt.Printf("%s\n", list)
	// output: abc

	array := [4]byte{96, 97, 98, 99}
	fmt.Printf("%s\n", array)
	// output: `abc
}

自定义样式

通过实现fmt.Stringer接口来定制类型的%s输出。下面的代码定义了一个自定义类型MyInteger,并实现了fmt.Stringer接口,返回一个字符串"funny"。因此无论该类型变量中实际存储的值是什么,%s得到的格式化字符串永远都为"funny",即fmt.Stringer接口的返回值。

package main

import "fmt"

type MyInteger int

func (i MyInteger) String() string {
	return "funny"
}

func main() {
	var demo1 MyInteger = 23
	fmt.Printf("%s\n", demo1)
	// output: funny

	var demo2 MyInteger = 298
	fmt.Printf("%s\n", demo2)
	// output: funny
}

%d

使用格式

%[+][-][length]d
+可选,表示右对齐。默认情况下为右对齐
-可选,表示左对齐
length可选,一个大于0的整数,表示格式化宽度

格式化整型

%d输出整数类型。%d可以将int, int32, int64, uint, uint8, uint16, uint32, uint64和与它们底层类型相同的类型输出。

package main

import "fmt"

func main() {
	var number int = 0
	fmt.Printf("The value of number is: %d.\n", number)
	// output: The value of number is: 0.
	
	var number32 int32 = 1
	fmt.Printf("The value of number32 is: %d.\n", number32)
	// output: The value of number32 is: 1.
	
	var number64 int64 = 2
	fmt.Printf("The value of number64 is: %d.\n", number64)
	// output: The value of number64 is: 2.
	
	var numberu uint = 3
	fmt.Printf("The value of numberu is: %d.\n", numberu)
	// output: The value of numberu is: 3.
	
	var numberu8 uint8 = 4
	fmt.Printf("The value of numberu8 is: %d.\n", numberu8)
	// output: The value of numberu8 is: 4.
	
	var numberu16 uint16 = 5
	fmt.Printf("The value of numberu16 is: %d.\n", numberu16)
	// output: The value of numberu16 is: 5.
	
	var numberu32 uint32 = 6
	fmt.Printf("The value of numberu32 is: %d.\n", numberu32)
	// output: The value of numberu32 is: 6.
	
	var numberu64 uint64 = 7
	fmt.Printf("The value of numberu64 is: %d.\n", numberu64)
	// output: The value of numberu64 is: 7.

	type MyInt int
	var myNumber MyInt = MyInt(number)
	fmt.Printf("The value of myNumber is: %d.\n", myNumber)
	// output: The value of myNumber is: 0.
}

%b,%#b

格式化整型

%b输出整数类型对应的二进制表示形式。%b可以将int, int32, int64, uint, uint8, uint16, uint32, uint64和与它们底层类型相同的类型输出。

package main

import "fmt"

func main() {
	var number int = -1
	fmt.Printf("The value of number is: %b.\n", number)
	// output: The value of number is: -1.

	var number32 int32 = 1
	fmt.Printf("The value of number32 is: %b.\n", number32)
	// output: The value of number32 is: 1.

	var number64 int64 = 2
	fmt.Printf("The value of number64 is: %b.\n", number64)
	// output: The value of number64 is: 10.

	var numberu uint = 3
	fmt.Printf("The value of numberu is: %b.\n", numberu)
	// output: The value of numberu is: 11.

	var numberu8 uint8 = 4
	fmt.Printf("The value of numberu8 is: %b.\n", numberu8)
	// output: The value of numberu8 is: 100.

	var numberu16 uint16 = 5
	fmt.Printf("The value of numberu16 is: %b.\n", numberu16)
	// output: The value of numberu16 is: 101.

	var numberu32 uint32 = 6
	fmt.Printf("The value of numberu32 is: %b.\n", numberu32)
	// output: The value of numberu32 is: 110.

	var numberu64 uint64 = 7
	fmt.Printf("The value of numberu64 is: %b.\n", numberu64)
	// output: The value of numberu64 is: 111.

	type MyInt int
	var myNumber MyInt = MyInt(number)
	fmt.Printf("The value of myNumber is: %b.\n", myNumber)
	// output: The value of myNumber is: -1.
}

与%b类似,%#b会在输出的结果添加0b前缀:

package main

import "fmt"

func main() {
	fmt.Printf("%#b\n", 23)
	// output: 0b10111
}

%o,%#o

%o输出整数类型对应的八进制表示形式。%o可以将int, int32, int64, uint, uint8, uint16, uint32, uint64和与它们底层类型相同的类型输出。

package main

import "fmt"

func main() {
	var number int = -10
	fmt.Printf("The value of number is: %o.\n", number)
	// output: The value of number is: -12.

	var number32 int32 = 23
	fmt.Printf("The value of number32 is: %o.\n", number32)
	// output: The value of number32 is: 27.

	var number64 int64 = 32
	fmt.Printf("The value of number64 is: %o.\n", number64)
	// output: The value of number64 is: 40.

	var numberu uint = 64
	fmt.Printf("The value of numberu is: %o.\n", numberu)
	// output: The value of numberu is: 100.

	var numberu8 uint8 = 128
	fmt.Printf("The value of numberu8 is: %o.\n", numberu8)
	// output: The value of numberu8 is: 200.

	var numberu16 uint16 = 216
	fmt.Printf("The value of numberu16 is: %o.\n", numberu16)
	// output: The value of numberu16 is: 330.

	var numberu32 uint32 = 512
	fmt.Printf("The value of numberu32 is: %o.\n", numberu32)
	// output: The value of numberu32 is: 1000.

	var numberu64 uint64 = 1024
	fmt.Printf("The value of numberu64 is: %o.\n", numberu64)
	// output: The value of numberu64 is: 2000.

	type MyInt int
	var myNumber MyInt = MyInt(number)
	myNumber = 2048
	fmt.Printf("The value of myNumber is: %o.\n", myNumber)
	// output: The value of myNumber is: 4000.
}

与%o类似,%#o会在输出的结果添加前缀0:

package main

import "fmt"

func main() {
	fmt.Printf("%#o\n", 23)
	// output: 027
}

%x, %#x

格式化整型

%x输出整型的十六进制形式,%x可以将int, int32, int64, uint, uint8, uint16, uint32, uint64和与它们底层类型相同的类型输出。

package main

import "fmt"

func main() {
	var number int = -10
	fmt.Printf("The value of number is: %x.\n", number)
	// output: The value of number is: -a.

	var number32 int32 = 23
	fmt.Printf("The value of number32 is: %x.\n", number32)
	// output: The value of number32 is: 17.

	var number64 int64 = 32
	fmt.Printf("The value of number64 is: %x.\n", number64)
	// output: The value of number64 is: 20.

	var numberu uint = 64
	fmt.Printf("The value of numberu is: %x.\n", numberu)
	// output: The value of numberu is: 40.

	var numberu8 uint8 = 128
	fmt.Printf("The value of numberu8 is: %x.\n", numberu8)
	// output: The value of numberu8 is: 80.

	var numberu16 uint16 = 216
	fmt.Printf("The value of numberu16 is: %x.\n", numberu16)
	// output: The value of numberu16 is: d8.

	var numberu32 uint32 = 512
	fmt.Printf("The value of numberu32 is: %x.\n", numberu32)
	// output: The value of numberu32 is: 200.

	var numberu64 uint64 = 1024
	fmt.Printf("The value of numberu64 is: %x.\n", numberu64)
	// output: The value of numberu64 is: 400.

	type MyInt int
	var myNumber MyInt = MyInt(number)
	myNumber = 2048
	fmt.Printf("The value of myNumber is: %x.\n", myNumber)
	// output: The value of myNumber is: 800.
}

格式化字符

如果对应的参数是字符,那么%x会将对应的Unicode码值转换为16进制:

package main

import "fmt"

func main() {
	c := 'a'
	fmt.Printf("%x, %x\n", c, 97)
	// output: 61, 61
}

格式化字符串

如果为空字符串,则%x不进行任何转换:

package main

import "fmt"

func main() {
	s := ""
	fmt.Printf("--%x--%x--", s, 97)
	// output: ----61--
}

对于长度大于1的字符串,%x将每个字符对应的Unicode码值转换成十六进制,然后将这些十六进制字符串拼接起来,而不是累加:

package main

import "fmt"

func main() {
	s := "aa"
	fmt.Printf("--%x--%x--", s, 97)
	// output: --6161--61--
}

最后与%x类似,%#x会在输出的结果添加前缀0x:

package main

import "fmt"

func main() {
	fmt.Printf("%#x\n", 23)
	// output: 0x17
}

%X, %#X

%X与%x功能类似,只是将%x输出后的小写字母转换为大写。

package main

import "fmt"

func main() {
	number := 30
	fmt.Printf("%X\n", number)
	// output: 1E

	character := 'b'
	fmt.Printf("%X\n", character)
	// output: 62

	position := "zoo"
	fmt.Printf("%X\n", position)
	// output: 7A6F6F
}

%v

%v输出值的本来格式,即值的相应默认格式。

package main

import "fmt"

func main() {
	number := 20
	fmt.Printf("%v~%v\n", number, &number)
	// output: 20~0xc00000a0b0

	floatNumber := 23.12
	fmt.Printf("%v\n", floatNumber)
	// output: 23.12

	slice := []int{3, 4, 5}
	fmt.Printf("%v\n", slice)
	// output: [3 4 5]

	array := [2]bool{true, false}
	fmt.Printf("%v\n", array)
	// output: [true false]

	m := make(map[string]interface{})
	m["date"] = "2021-05-08T14:38:00"
	m["type"] = "Golang"
	fmt.Printf("%v\n", m)
	// output: map[date:2021-05-08T14:38:00 type:Golang]

	channel := make(chan int, 2)
	channel <- 2
	fmt.Printf("%v\n", channel)
	// output: 0xc00003e070
}

%+v

%v输出值的默认格式,当遇到结构体类型时,%v仅将结构体的成员变量输出,但不会输出成员变量和成员类型的对应关系,而%+v则会做到这一点。除此之外,%+v与%v没有什么不同。

package main

import "fmt"

type Demo struct {
	a int
	s string
}

func main() {
	d := Demo{1, "q"}
	fmt.Printf("v:  %v, %v\n", d, &d)
	// output: v:  {1 q}, &{1 q}
	fmt.Printf("v+: %+v, %+v\n", d, &d)
	// output: +v: {a:1 s:q}, &{a:1 s:q}
}

%#v

%#v输出Go语法格式的值,通常在debug的过程中,%#v要远比%v好用的多,因为它能显示更多有用的信息。看看它与%+v有什么不同之处:

package main

import "fmt"

type Demo struct {
	a int
	s string
}

func main() {
	d := Demo{1, "q"}
	fmt.Printf("v:  %v, %v\n", d, &d)
	// output: v:  {1 q}, &{1 q}
	fmt.Printf("v+: %+v, %+v\n", d, &d)
	// output: +v: {a:1 s:q}, &{a:1 s:q}
	fmt.Printf("#v: %#v, %#v\n", d, &d)
	// output: #v: main.Demo{a:1, s:"q"}, &main.Demo{a:1, s:"q"}
}

自定义样式

通过实现fmt.GoStringer接口来定制类型的%#v输出。下面的代码定义了一个自定义类型MyInteger,并实现了fmt.GoStringer接口,返回一个字符串"funny"。因此无论该类型变量中实际存储的值是什么,%#v得到的格式化字符串永远都为"funny",即fmt.GoStringer接口的返回值。

package main

import "fmt"

type MyInteger int

func (i MyInteger) GoString() string {
	return "funny"
}

func main() {
	var demo1 MyInteger = 23
	fmt.Printf("%#v\n", demo1)
	// output: funny

	var demo2 MyInteger = 298
	fmt.Printf("%#v\n", demo2)
	// output: funny
}

%t

%t用于格式化布尔值或结果为布尔值的表达式的输出。

package main

import "fmt"

func main() {
	fmt.Printf("%t\n", true)
	// output: true
	fmt.Printf("%t\n", false)
	// output: false

	a := true
	b := false
	fmt.Printf("%t, %t\n", a, b)
	// output: true, false
	fmt.Printf("%t\n", 1 == 2)
	// output: false
}

%T

%T输出参数对应的Go语言类型。

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Printf("%T\n", true)
	// output: bool

	file, err := os.Open("student.json")
	fmt.Printf("%T, %T\n", file, err)
	// output: *os.File, <nil>
}

%%

两个百分号(%%)用来表示一个%输出。

package main

import "fmt"

func main() {
	fmt.Printf("%%")
	// output: %
}

%c

输出Unicode码对应的字符。

package main

import "fmt"

func main() {
	fmt.Printf("%c", 971)
	// output: ϋ
}

%U

将一个Unicode值转换为对应的Unicode码点。

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%U\n", 1)
	// output: U+0001
}

%f

格式化浮点数,默认情况下,最多只能格式化小数点后六位。如果小数部分大于6位,则进行四舍五入保留前六位格式化。

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%f\n", 3.1415926)
	// output: 3.141593
}

%e

%e与%f一样,都会对浮点数进行格式化。与%f不同的是,%e将浮点数格式化为科学计数法显示。

package main

import "fmt"

func main() {
	fmt.Printf("%e", 1234123.312)
	// output: 1.234123e+06
}
格式化宽度

在%和格式化类型之间添加一个自然数,用来表示显示宽度。例如%20s表示将被格式化的字符串显示20个宽度。

package main

import "fmt"

func main() {
	fmt.Printf("---%20s---\n", "TTimeCat")
	fmt.Printf("%3d\n", 23)
	// outputs:
	// ---            TTimeCat---
	// 23
}

如果格式化后的字符串长度超过了规定的字符显示宽度,那么规定的显示宽度失效:

package main

import "fmt"

func main() {
	fmt.Printf("---%1s---\n", "TTimeCat")
	// output: ---TTimeCat---
}
指定精度

对于浮点数,我们可以指定精度显示:

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Printf("%3.9f\n", math.Pi)
	fmt.Printf("%3.10f\n", math.Pi)
	// outputs:
	// 3.141592654
	// 3.1415926536
}

通过这个例子可以看出,指定精度的格式化会自动的四舍五入。对于小数部分长度没超过精度要求的浮点数,会在末尾补零:

package main

import "fmt"

func main() {
	pi := 3.14
	fmt.Printf("%.5f\n", pi)
	// output: 3.14000
}
左对齐

默认情况下格式化字符串的对齐方式是右对齐。添加“-”可以设置为左对齐。

package main

import "fmt"

func main() {
	name := "TTimeCat"
	fmt.Printf("My name is %10s.\n", name)
	fmt.Printf("My name is %-10s.\n", name)
	// outputs:
	// My name is   TTimeCat.
	// My name is TTimeCat  .
}
参数和参数列表

每一个格式化样式字符一个参数。多个参数以逗号分隔,组成参数列表。参数的个数、顺序和类型必须与格式化字符一一对应,否则回出现格式化字符串没有达到预期的效果。

格式化字符与参数数量不匹配

下面的例子是一个参数与格式化字符数量不匹配的问题:

package main

import "fmt"

func main() {
	fmt.Printf("Hi, My name is: %s. I am %d years old.", "TCatTime")
	// output: Hi, My name is: TCatTime. I am %!d(MISSING) years old.
}

从上面的例子可以看出,参数数量小于格式化字符的数量时,得到的格式化字符串会显示没有匹配到参数的格式化字符串。如果参数数量大于格式化字符的数量,得到的格式化字符串会在末尾加上相关的提示:

package main

import "fmt"

func main() {
	fmt.Printf("Hi, My name is: %s. I am %d years old.", "TCatTime", 15, 23)
	// output: Hi, My name is: TCatTime. I am 15 years old.%!(EXTRA int=23)
}

格式化字符与参数类型不匹配

当格式化字符与参数类型不匹配时,也会影响格式化字符串的结果。例如把一个整型数字和%s匹配:

package main

import "fmt"

func main() {
	fmt.Printf("I am %s years old.", 15)
	// output: I am %!s(int=15) years old
}