Go 语言 fmt 包与占位符

失去的东西,其实从来未曾真正地属于你,也不必惋惜,始终真心真意

Posted by yishuifengxiao on 2024-02-14

基础占位符概览

通用占位符

占位符 说明 示例 输出示例
%v 默认格式的值 fmt.Printf("%v", data) {John 30}
%+v 结构体字段名+值 fmt.Printf("%+v", p) {Name:John Age:30}
%#v Go 语法表示的值 fmt.Printf("%#v", p) main.Person{Name:"John", Age:30}
%T 值类型 fmt.Printf("%T", p) main.Person
%% 字面百分号 fmt.Printf("%%") %

布尔值占位符

占位符 说明 示例 输出
%t 布尔值 fmt.Printf("%t", true) true

整数占位符

占位符 说明 示例 输出
%b 二进制 fmt.Printf("%b", 5) 101
%c Unicode 字符 fmt.Printf("%c", 65) A
%d 十进制 fmt.Printf("%d", 0x12) 18
%o 八进制 fmt.Printf("%o", 10) 12
%O 带 0o 前缀八进制 fmt.Printf("%O", 10) 0o12
%q 单引号字符 fmt.Printf("%q", 65) 'A'
%x 十六进制 (小写) fmt.Printf("%x", 255) ff
%X 十六进制 (大写) fmt.Printf("%X", 255) FF
%U Unicode 格式 fmt.Printf("%U", 'A') U+0041

浮点数与复数占位符

占位符 说明 示例 输出
%b 指数为二的幂的科学计数 fmt.Printf("%b", 3.5) 7874384p-51
%e 科学计数法 (小写 e) fmt.Printf("%e", 1000.0) 1.000000e+03
%E 科学计数法 (大写 E) fmt.Printf("%E", 1000.0) 1.000000E+03
%f 小数表示 fmt.Printf("%f", 3.1415926535) 3.141593
%F %f 同上 同上
%g 紧凑表示 (根据情况) fmt.Printf("%g", 3.1415926535) 3.1415926535
%G %g (大写 E) fmt.Printf("%G", 3.1415926535) 同上

字符串与字节切片

占位符 说明 示例 输出
%s 字符串或字节切片 fmt.Printf("%s", []byte("hello")) hello
%q 双引号字符串 fmt.Printf("%q", "hello") "hello"
%x 十六进制 (小写) fmt.Printf("%x", "hello") 68656c6c6f
%X 十六进制 (大写) fmt.Printf("%X", "hello") 68656C6C6F

指针

占位符 说明 示例 输出示例
%p 指针地址 fmt.Printf("%p", &v) 0xc0000180a0
%#p 不带 0x 前缀地址 fmt.Printf("%#p", &v) c0000180a0

高级格式化技巧

宽度与精度控制

// 宽度控制
fmt.Printf("|%6d|", 123) // | 123|
fmt.Printf("|%6s|", "abc") // | abc|
fmt.Printf("|%-6s|", "abc") // |abc | (左对齐)

// 精度控制
fmt.Printf("%.2f", 3.14159) // 3.14
fmt.Printf("%.5s", "hello world") // hello

// 组合使用
fmt.Printf("|%6.2f|", 3.14159) // | 3.14|
fmt.Printf("|%-8.4s|", "golang") // |gol |

适用场景

  • 表格数据对齐
  • 限制浮点数小数位数
  • 截断长字符串
  • 报告和日志的格式化输出

参数索引

fmt.Printf("%[2]d %[1]d", 1, 2)  // 2 1
fmt.Printf("%[3]*.[2]*[1]f", 3.14159, 2, 10)
// 相当于 fmt.Printf("%10.2f", 3.14159) → " 3.14"

适用场景

  • 重用参数
  • 改变参数顺序
  • 从参数列表中获取宽度/精度值

填充与标志

// 标志字符
fmt.Printf("%+d", 123) // +123 (显示正负号)
fmt.Printf("% d", 123) // 123 (正数前留空格)
fmt.Printf("%#x", 255) // 0xff (添加前缀)

// 填充字符
fmt.Printf("%06d", 123) // 000123
fmt.Printf("%*s", 10, " ") // 10个空格

适用场景与最佳实践

调试与开发

// 快速查看变量
fmt.Printf("%#v\n", complexStruct)
// 输出: main.User{Name:"Alice", Age:30, Tags:[]string{"admin", "editor"}}

// 类型检查
fmt.Printf("Type: %T\n", unknownVar)
// 输出: Type: []map[string]int

// 指针地址比较
fmt.Printf("Pointer: %p\n", &obj1)
fmt.Printf("Pointer: %p\n", &obj2)

推荐占位符%#v, %T, %p

日志记录

// 结构化日志
log.Printf("[%s] %-15s %s:%d - %s",
time.Now().Format("2006-01-02 15:04:05"),
"INFO",
filepath.Base(file),
line,
message)

// 错误日志
err := errors.New("file not found")
log.Printf("Operation failed: %+v", err)

推荐占位符%s, %d, %v, %+v

报表生成

// 表格输出
fmt.Println("| Name | Age | Score |")
fmt.Println("|------------|-----|----------|")
fmt.Printf("| %-10s | %3d | %8.2f |\n", "Alice", 28, 95.5)
fmt.Printf("| %-10s | %3d | %8.2f |\n", "Bob", 32, 88.75)
fmt.Printf("| %-10s | %3d | %8.2f |\n", "Charlie", 25, 92.0)

// 输出:
// | Name | Age | Score |
// |------------|-----|----------|
// | Alice | 28 | 95.50 |
// | Bob | 32 | 88.75 |
// | Charlie | 25 | 92.00 |

推荐占位符%-Ns (左对齐字符串), %Nd (右对齐数字), %N.Mf (浮点数)

字符串处理

// 安全输出
str := `Hello "World"`
fmt.Printf("%q", str) // "Hello \"World\""

// 十六进制查看
data := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}
fmt.Printf("% x", data) // 48 65 6c 6c 6f
fmt.Printf("%X", data) // 48656C6C6F

// 截断长文本
longText := "This is a very long string that needs truncation"
fmt.Printf("%.20s...", longText) // This is a very long...

推荐占位符%q, %x, %X, %.Ns

网络与系统编程

// IP地址格式化
ip := net.ParseIP("192.168.1.1")
fmt.Printf("%v", ip) // 192.168.1.1
fmt.Printf("%#v", ip) // net.IP{0xc0, 0xa8, 0x1, 0x1}

// 时间格式化
now := time.Now()
fmt.Printf("%d-%02d-%02d", now.Year(), now.Month(), now.Day())
// 输出: 2023-07-15

// 文件权限
mode := os.FileMode(0644)
fmt.Printf("%04o", mode) // 0644

推荐占位符%v, %#v, %d, %o

数值计算

// 科学计数法
largeNumber := 1234567890.12345
fmt.Printf("%e\n", largeNumber) // 1.234568e+09
fmt.Printf("%g\n", largeNumber) // 1.23456789012345e+09

// 精确小数
pi := math.Pi
fmt.Printf("%.10f", pi) // 3.1415926536

// 二进制表示
flags := 0b1010
fmt.Printf("%b", flags) // 1010

推荐占位符%e, %E, %f, %.Nf, %b

高级应用技巧

自定义格式化

type Point struct {
X, Y int
}

func (p Point) Format(f fmt.State, verb rune) {
switch verb {
case 'v':
if f.Flag('+') {
fmt.Fprintf(f, "Point(X=%d, Y=%d)", p.X, p.Y)
} else {
fmt.Fprintf(f, "(%d, %d)", p.X, p.Y)
}
case 's':
fmt.Fprintf(f, "X:%d Y:%d", p.X, p.Y)
}
}

func main() {
p := Point{3, 4}
fmt.Printf("%v\n", p) // (3, 4)
fmt.Printf("%+v\n", p) // Point(X=3, Y=4)
fmt.Printf("%s\n", p) // X:3 Y:4
}

复杂格式构建

// 动态格式字符串
format := func(width, precision int) string {
return fmt.Sprintf("%%%d.%df", width, precision)
}

fmt.Printf(format(10, 3), math.Pi) // 3.142

// 多语言支持
type Language int
const (
En Language = iota
Zh
)

func (l Language) DateFormat() string {
switch l {
case En:
return "January 02, 2006"
case Zh:
return "2006年01月02日"
default:
return "2006-01-02"
}
}

func printLocalizedDate(lang Language, t time.Time) {
fmt.Printf(t.Format(lang.DateFormat()))
}

性能优化

// 使用 bytes.Buffer 代替 fmt.Sprintf 构建大字符串
var buf bytes.Buffer
for i := 0; i < 1000; i++ {
fmt.Fprintf(&buf, "Item %d: %f\n", i, math.Sqrt(float64(i)))
}
result := buf.String()

// 预分配内存
sb := strings.Builder{}
sb.Grow(1024) // 预分配1KB
fmt.Fprintf(&sb, "Header: %s\n", title)

fmt 包函数详解

输出函数

函数 说明 使用场景
Print(a ...any) 标准输出 简单调试输出
Printf(format string, a ...any) 格式化输出 需要格式化的输出
Println(a ...any) 输出并换行 多值输出,自动加空格
Fprint(w io.Writer, a ...any) 输出到 Writer 写入文件、网络连接等
Fprintf(w io.Writer, format string, a ...any) 格式化输出到 Writer 结构化日志写入
Fprintln(w io.Writer, a ...any) 输出到 Writer 并换行 写入多行内容
Sprint(a ...any) string 返回字符串 构建字符串
Sprintf(format string, a ...any) string 返回格式化字符串 创建格式化字符串
Sprintln(a ...any) string 返回字符串并加空格换行 构建多值字符串

输入函数

函数 说明 使用场景
Scan(a ...any) 从标准输入读取 简单输入读取
Scanf(format string, a ...any) 格式化读取 结构化输入解析
Scanln(a ...any) 读取一行 命令行工具输入
Fscan(r io.Reader, a ...any) 从 Reader 读取 从文件或网络读取
Fscanf(r io.Reader, format string, a ...any) 格式化从 Reader 读取 解析结构化数据文件
Fscanln(r io.Reader, a ...any) 从 Reader 读取一行 解析行格式数据
Sscan(str string, a ...any) 从字符串读取 解析字符串数据
Sscanf(str string, format string, a ...any) 格式化从字符串读取 复杂字符串解析
Sscanln(str string, a ...any) 从字符串读取一行 解析行格式字符串

实用示例集合

命令行进度条

func showProgress(current, total int) {
percent := float64(current) / float64(total) * 100
barWidth := 50
filled := int(float64(barWidth) * current / total

fmt.Printf("\r[%s%s] %.2f%%",
strings.Repeat("=", filled),
strings.Repeat(" ", barWidth-filled),
percent)

if current == total {
fmt.Println()
}
}

十六进制转储

func hexDump(data []byte) {
for i := 0; i < len(data); i += 16 {
// 偏移量
fmt.Printf("%08x ", i)

// 十六进制
for j := 0; j < 16; j++ {
if i+j < len(data) {
fmt.Printf("%02x ", data[i+j])
} else {
fmt.Print(" ")
}
if j == 7 {
fmt.Print(" ")
}
}

fmt.Print(" ")

// ASCII
for j := 0; j < 16; j++ {
if i+j < len(data) {
b := data[i+j]
if b >= 32 && b <= 126 {
fmt.Printf("%c", b)
} else {
fmt.Print(".")
}
}
}
fmt.Println()
}
}

表格数据对齐

func printTable(data [][]string) {
// 计算列宽
colWidths := make([]int, len(data[0]))
for _, row := range data {
for i, cell := range row {
if len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}

// 打印表格
for _, row := range data {
for i, cell := range row {
fmt.Printf("| %-*s ", colWidths[i], cell)
}
fmt.Println("|")
}
}

常见陷阱与最佳实践

常见错误

// 1. 参数类型不匹配
var count int = 5
fmt.Printf("%s", count) // 错误:%!s(int=5)

// 2. 忘记处理错误
n, err := fmt.Sscanf(input, "%d", &value)
// 应该检查 err 和 n

// 3. 性能问题(大量小字符串拼接)
var s string
for i := 0; i < 10000; i++ {
s += fmt.Sprintf("%d", i) // 低效
}

// 正确方式
var builder strings.Builder
for i := 0; i < 10000; i++ {
fmt.Fprintf(&builder, "%d", i)
}
s := builder.String()