Go 语言 fmt.Printf 格式化输出详解
Go 语言的 fmt 包是标准库中使用最频繁的包之一,提供了强大的格式化输入输出功能。
1. 基础概念
fmt 包的三大打印函数
在 Go 语言中,fmt 包提供了三个主要的打印函数,各有不同的特点:
| 函数 | 特点 | 使用场景 |
|---|---|---|
fmt.Print | 不格式化,不自动换行,变量间无空格 | 简单的连续输出 |
fmt.Println | 不格式化,自动换行,变量间有空格 | 调试和简单输出 |
fmt.Printf | 支持格式化,需手动换行 | 精确控制输出格式 |
示例代码:
go
package main
import "fmt"
func main() {
// Print: 不格式化,不自动换行
fmt.Print("hello", "world\n")
// Println: 不格式化,自动换行,变量间有空格
fmt.Println("hello", "world")
// Printf: 支持格式化,需手动换行
fmt.Printf("hello world\n")
}运行结果:
shell
helloworld
hello world
hello world为什么重点学习 Printf?
虽然 Print 和 Println 使用简单,但 Printf 提供了更强大的功能:
- 精确控制格式: 可以指定数值的进制、精度、宽度等
- 类型安全: 通过占位符确保类型匹配
- 可读性强: 格式化字符串清晰表达输出意图
- 功能丰富: 支持各种数据类型的专门格式化
2. Printf 函数详解
函数签名
go
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}参数说明:
- format: 格式化字符串,包含文本和占位符
- a ...interface{}: 可变参数,对应占位符的值
- 返回值: 写入的字节数和可能的错误
占位符概念
占位符是以 % 开头的特殊代码,用于指定变量的输出格式。格式为:%[flags][width][.precision]verb
快速入门示例
以数值进制转换为例,演示占位符的基本用法:
go
package main
import "fmt"
func main() {
n := 1024
fmt.Printf("%d 的二进制:%b\n", n, n)
fmt.Printf("%d 的八进制:%o\n", n, n)
fmt.Printf("%d 的十进制:%d\n", n, n)
fmt.Printf("%d 的十六进制:%x\n", n, n)
}运行结果:
shell
1024 的二进制:10000000000
1024 的八进制:2000
1024 的十进制:1024
1024 的十六进制:400占位符说明:
%d: 十进制整数%b: 二进制整数%o: 八进制整数%x: 十六进制整数(小写字母)
3. 占位符完整参考
通用占位符
通用占位符适用于任何数据类型:
| 占位符 | 说明 | 示例输出 |
|---|---|---|
%v | 默认格式 | {Alice female 25} |
%+v | 结构体显示字段名 | {name:Alice gender:female age:25} |
%#v | Go 语法表示 | main.Profile{name:"Alice", gender:"female", age:25} |
%T | 类型信息 | main.Profile |
%% | 百分号字面值 | % |
示例代码:
go
package main
import "fmt"
type Profile struct {
name string
gender string
age int
}
func main() {
person := Profile{name: "Alice", gender: "female", age: 25}
fmt.Printf("默认格式: %v\n", person)
fmt.Printf("带字段名: %+v\n", person)
fmt.Printf("Go语法: %#v\n", person)
fmt.Printf("类型: %T\n", person)
fmt.Printf("百分号: %%\n")
}运行结果:
shell
默认格式: {Alice female 25}
带字段名: {name:Alice gender:female age:25}
Go语法: main.Profile{name:"Alice", gender:"female", age:25}
类型: main.Profile
百分号: %布尔值占位符
| 占位符 | 说明 | 示例 |
|---|---|---|
%t | 布尔值 | true, false |
示例代码:
go
package main
import "fmt"
func main() {
fmt.Printf("真值: %t\n", true)
fmt.Printf("假值: %t\n", false)
fmt.Printf("表达式: %t\n", 10 > 5)
}运行结果:
shell
真值: true
假值: false
表达式: true字符串占位符
| 占位符 | 说明 | 示例输出 |
|---|---|---|
%s | 字符串或字节切片 | Hello, Go |
%q | 带双引号的字符串 | "Hello, Go" |
%x | 十六进制(小写) | 48656c6c6f |
%X | 十六进制(大写) | 48656C6C6F |
示例代码:
go
package main
import "fmt"
func main() {
text := "Hello, Go"
bytes := []byte("Hello, Go")
fmt.Printf("字符串: %s\n", text)
fmt.Printf("字节切片: %s\n", bytes)
fmt.Printf("带引号: %q\n", text)
fmt.Printf("转义字符: %q\n", "hello\tworld\n")
fmt.Printf("十六进制(小写): %x\n", text)
fmt.Printf("十六进制(大写): %X\n", text)
}运行结果:
shell
字符串: Hello, Go
字节切片: Hello, Go
带引号: "Hello, Go"
转义字符: "hello\tworld\n"
十六进制(小写): 48656c6c6f2c20476f
十六进制(大写): 48656C6C6F2C20476F指针占位符
| 占位符 | 说明 | 示例输出 |
|---|---|---|
%p | 指针地址 | 0xc0000a6150 |
示例代码:
go
package main
import "fmt"
func main() {
x := 42
fmt.Printf("变量值: %d\n", x)
fmt.Printf("变量地址: %p\n", &x)
}运行结果:
shell
变量值: 42
变量地址: 0xc0000160e0整型占位符
| 占位符 | 说明 | 示例输出 |
|---|---|---|
%b | 二进制 | 1010 |
%d | 十进制 | 10 |
%o | 八进制 | 12 |
%x | 十六进制(小写) | a |
%X | 十六进制(大写) | A |
%c | Unicode 字符 | A |
%q | 带单引号的字符 | 'A' |
%U | Unicode 码点 | U+0041 |
示例代码:
go
package main
import "fmt"
func main() {
n := 1024
// 不同进制表示
fmt.Printf("%d 的二进制:%b\n", n, n)
fmt.Printf("%d 的八进制:%o\n", n, n)
fmt.Printf("%d 的十进制:%d\n", n, n)
fmt.Printf("%d 的十六进制:%x\n", n, n)
// 字符相关
fmt.Printf("ASCII %d 对应字符:%c\n", 65, 65)
fmt.Printf("Unicode 字符:%c\n", 0x4E2D)
fmt.Printf("带引号字符:%q\n", 0x4E2D)
fmt.Printf("Unicode 码点:%U\n", '中')
}运行结果:
shell
1024 的二进制:10000000000
1024 的八进制:2000
1024 的十进制:1024
1024 的十六进制:400
ASCII 65 对应字符:A
Unicode 字符:中
带引号字符:'中'
Unicode 码点:U+4E2D浮点数占位符
| 占位符 | 说明 | 示例输出 |
|---|---|---|
%e | 科学计数法(小写e) | 1.234000e+01 |
%E | 科学计数法(大写E) | 1.234000E+01 |
%f | 小数形式 | 12.340000 |
%F | 等价于 %f | 12.340000 |
%g | 自动选择 %e 或 %f | 12.34 |
%G | 自动选择 %E 或 %F | 12.34 |
示例代码:
go
package main
import "fmt"
func main() {
f1 := 12.34
f2 := 1234567.89
fmt.Printf("小数: %f\n", f1)
fmt.Printf("科学计数法(小写): %e\n", f1)
fmt.Printf("科学计数法(大写): %E\n", f1)
fmt.Printf("自动格式: %g\n", f1)
fmt.Printf("大数自动格式: %g\n", f2)
fmt.Printf("大数科学计数法: %e\n", f2)
}运行结果:
shell
小数: 12.340000
科学计数法(小写): 1.234000e+01
科学计数法(大写): 1.234000E+01
自动格式: 12.34
大数自动格式: 1.234568e+06
大数科学计数法: 1.234568e+064. 格式控制
宽度和精度控制
格式化占位符支持宽度和精度控制,语法为:%[flags][width][.precision]verb
| 控制符 | 说明 | 示例 |
|---|---|---|
width | 最小宽度 | %5d |
.precision | 精度 | %.2f |
width.precision | 宽度和精度 | %8.2f |
示例代码:
go
package main
import "fmt"
func main() {
n := 12.34
fmt.Printf("默认格式: %f\n", n)
fmt.Printf("宽度9: %9f\n", n)
fmt.Printf("精度2: %.2f\n", n)
fmt.Printf("宽度9精度2: %9.2f\n", n)
fmt.Printf("宽度9精度0: %9.f\n", n)
// 整数宽度控制
fmt.Printf("整数宽度5: %5d\n", 42)
fmt.Printf("整数宽度5补零: %05d\n", 42)
}运行结果:
shell
默认格式: 12.340000
宽度9: 12.340000
精度2: 12.34
宽度9精度2: 12.34
宽度9精度0: 12
整数宽度5: 42
整数宽度5补零: 00042标志符(Flags)
标志符用于修改输出格式,常用的标志符包括:
| 标志符 | 说明 | 示例 |
|---|---|---|
+ | 显示符号 | %+d → +42 |
- | 左对齐 | %-5d → 42 |
0 | 零填充 | %05d → 00042 |
# | 替代格式 | %#x → 0x2a |
(空格) | 正数前加空格 | % d → 42 |
+ 标志符示例
示例代码:
go
package main
import "fmt"
type Profile struct {
name string
gender string
age int
}
func main() {
person := Profile{name: "Alice", gender: "female", age: 25}
// 结构体字段名
fmt.Printf("普通格式: %v\n", person)
fmt.Printf("带字段名: %+v\n", person)
// ASCII 编码
fmt.Printf("普通字符串: %q\n", "Hello")
fmt.Printf("ASCII编码: %+q\n", "Hello")
fmt.Printf("中文字符串: %q\n", "中文")
fmt.Printf("Unicode编码: %+q\n", "中文")
}运行结果:
shell
普通格式: {Alice female 25}
带字段名: {name:Alice gender:female age:25}
普通字符串: "Hello"
ASCII编码: "Hello"
中文字符串: "中文"
Unicode编码: "\u4e2d\u6587"# 标志符示例
示例代码:
go
package main
import "fmt"
func main() {
// 十六进制前缀
fmt.Printf("十六进制: %x\n", "Hello")
fmt.Printf("带前缀: %#x\n", "Hello")
// 字符串格式
fmt.Printf("双引号: %q\n", "Hello")
fmt.Printf("反引号: %#q\n", "Hello")
// Unicode 格式
fmt.Printf("Unicode: %U\n", '中')
fmt.Printf("带字符: %#U\n", '中')
// 指针格式
x := 42
fmt.Printf("指针: %p\n", &x)
fmt.Printf("无前缀: %#p\n", &x)
}运行结果:
shell
十六进制: 48656c6c6f
带前缀: 0x48656c6c6f
双引号: "Hello"
反引号: `Hello`
Unicode: U+4E2D
带字符: U+4E2D '中'
指针: 0xc0000160e0
无前缀: c0000160e0对齐和填充
字符串对齐
示例代码:
go
package main
import "fmt"
func main() {
// 右对齐(默认)
fmt.Printf("右对齐: 'a%5sc'\n", "b")
// 左对齐
fmt.Printf("左对齐: 'a%-5sc'\n", "b")
// 零填充(仅适用于数字)
fmt.Printf("零填充: 'a%05dc'\n", 42)
// 超长不截断
fmt.Printf("超长: 'a%5sc'\n", "verylongstring")
}运行结果:
shell
右对齐: 'a bc'
左对齐: 'ab c'
零填充: 'a00042c'
超长: 'averylongstringc'浮点数对齐
示例代码:
go
package main
import "fmt"
func main() {
// 右对齐,宽度6,精度2
fmt.Printf("右对齐: %6.2f | %6.2f\n", 12.3, 123.4567)
// 左对齐,宽度6,精度2
fmt.Printf("左对齐: %-6.2f | %-6.2f\n", 12.3, 123.4567)
// 零填充,宽度8,精度2
fmt.Printf("零填充: %08.2f\n", 12.3)
}运行结果:
shell
右对齐: 12.30 | 123.46
左对齐: 12.30 | 123.46
零填充: 00012.30符号控制
使用空格标志符可以为正数预留符号位:
示例代码:
go
package main
import "fmt"
func main() {
fmt.Printf("正数: '% d'\n", 42)
fmt.Printf("负数: '% d'\n", -42)
fmt.Printf("强制符号: '%+d'\n", 42)
fmt.Printf("强制符号: '%+d'\n", -42)
}运行结果:
shell
正数: ' 42'
负数: '-42'
强制符号: '+42'
强制符号: '-42'5. 常用格式化模式
表格式输出
go
package main
import "fmt"
func main() {
fmt.Printf("%-10s | %5s | %8s\n", "姓名", "年龄", "工资")
fmt.Printf("%-10s | %5s | %8s\n", "------", "----", "--------")
fmt.Printf("%-10s | %5d | %8.2f\n", "张三", 25, 5000.50)
fmt.Printf("%-10s | %5d | %8.2f\n", "李四", 30, 7500.75)
fmt.Printf("%-10s | %5d | %8.2f\n", "王五", 28, 6200.00)
}调试输出
go
package main
import "fmt"
type User struct {
ID int
Name string
Age int
}
func main() {
user := User{ID: 1, Name: "Alice", Age: 25}
fmt.Printf("调试信息: %+v\n", user)
fmt.Printf("类型信息: %T\n", user)
fmt.Printf("Go语法: %#v\n", user)
}6. 最佳实践
- 选择合适的占位符: 根据数据类型选择最合适的占位符
- 控制输出精度: 对浮点数使用适当的精度控制
- 保持格式一致: 在同一应用中保持格式化风格的一致性
- 使用表格对齐: 输出表格数据时使用宽度控制对齐
- 调试时使用 %+v: 调试结构体时使用
%+v显示字段名