Skip to content

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?

虽然 PrintPrintln 使用简单,但 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}
%#vGo 语法表示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
%cUnicode 字符A
%q带单引号的字符'A'
%UUnicode 码点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等价于 %f12.340000
%g自动选择 %e 或 %f12.34
%G自动选择 %E 或 %F12.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+06

4. 格式控制

宽度和精度控制

格式化占位符支持宽度和精度控制,语法为:%[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
-左对齐%-5d42
0零填充%05d00042
#替代格式%#x0x2a
(空格)正数前加空格% 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. 最佳实践

  1. 选择合适的占位符: 根据数据类型选择最合适的占位符
  2. 控制输出精度: 对浮点数使用适当的精度控制
  3. 保持格式一致: 在同一应用中保持格式化风格的一致性
  4. 使用表格对齐: 输出表格数据时使用宽度控制对齐
  5. 调试时使用 %+v: 调试结构体时使用 %+v 显示字段名

7. 参考资料