第 36 课 - 格式化输出 fmt
学习目标
- 掌握
fmt包中常用的打印函数及其区别 - 熟练使用格式化占位符进行类型安全的输出
- 学会使用
Sprintf构建格式化字符串 - 了解错误输出和扫描输入的基本方法
核心概念
1. 三种基本打印函数
Go 的 fmt 包提供了三种主要的打印函数,它们各有特点:
Println- 最简单的输出,会在参数间添加空格,结尾自动换行Printf- 格式化输出,需要指定格式字符串和对应的参数Sprintf- 不直接输出,而是返回格式化后的字符串
// 三种函数的对比
package main
import "fmt"
func main() {
// Println - 参数间有空格,结尾换行
fmt.Println("Hello", "World", 123)
// 输出: Hello World 123\n
// Printf - 需要格式化字符串
fmt.Printf("Hello %s, age %d\n", "Tom", 25)
// 输出: Hello Tom, age 25\n
// Sprintf - 返回格式化后的字符串
msg := fmt.Sprintf("Hello %s, age %d", "Jerry", 30)
fmt.Println(msg)
// 输出: Hello Jerry, age 30
}
2. 常用格式化占位符
| 占位符 | 说明 | 示例 |
|---|---|---|
%d | 十进制整数 | fmt.Printf("%d", 100) → 100 |
%s | 字符串 | fmt.Printf("%s", "Go") → Go |
%f | 浮点数(默认6位小数) | fmt.Printf("%f", 3.14) → 3.140000 |
%T | 值的类型 | fmt.Printf("%T", 100) → int |
%v | 默认格式的值 | fmt.Printf("%v", []int{1,2}) → [1 2] |
%b | 二进制 | fmt.Printf("%b", 5) → 101 |
%x | 十六进制 | fmt.Printf("%x", 255) → ff |
3. 格式化宽度和精度控制
package main
import "fmt"
func main() {
// 宽度控制 - 最小字符数
fmt.Printf("|%10d|\n", 123) // 右对齐: | 123|
fmt.Printf("|%-10d|\n", 123) // 左对齐: |123 |
fmt.Printf("|%010d|\n", 123) // 零填充: |0000000123|
// 浮点数精度
fmt.Printf("|%.2f|\n", 3.14159) // 两位小数: |3.14|
fmt.Printf("|%8.2f|\n", 3.14159) // 宽度8,两位小数: | 3.14|
// 字符串宽度
fmt.Printf("|%20s|\n", "Go") // 右对齐: | Go|
fmt.Printf("|%-20s|\n", "Go") // 左对齐: |Go |
}
代码示例
示例1:基本格式化输出
package main
import (
"fmt"
"os"
)
type User struct {
Name string
Age int
City string
}
func main() {
// 1. 基本打印
fmt.Println("=== 基本打印 ===")
fmt.Println("Hello, Go!")
fmt.Println("数字:", 42, "布尔:", true)
// 2. 格式化输出
fmt.Println("\n=== 格式化输出 ===")
name := "张三"
age := 28
score := 89.5
fmt.Printf("姓名: %s, 年龄: %d, 分数: %.1f\n", name, age, score)
// 3. 复杂类型格式化
fmt.Println("\n=== 结构体格式化 ===")
user := User{
Name: "李四",
Age: 35,
City: "北京",
}
fmt.Printf("用户信息: %+v\n", user) // 包含字段名
fmt.Printf("类型: %T\n", user) // 显示类型
fmt.Printf("默认格式: %v\n", user) // 默认格式
// 4. 使用 Sprintf 构建字符串
fmt.Println("\n=== 构建字符串 ===")
filePath := fmt.Sprintf("/home/%s/%s.txt", user.Name, "document")
fmt.Println("文件路径:", filePath)
// 5. 错误输出到 stderr
fmt.Println("\n=== 错误输出 ===")
fmt.Fprintln(os.Stderr, "这是一个错误信息")
}
示例2:实用的格式化技巧
package main
import (
"fmt"
"time"
)
func main() {
// 1. 动态宽度
values := []struct {
name string
value int
}{
{"短名", 100},
{"这是一个很长的名字", 200},
}
fmt.Println("=== 动态宽度 ===")
for _, v := range values {
fmt.Printf("%-20s: %10d\n", v.name, v.value)
}
// 2. 科学计数法
fmt.Println("\n=== 科学计数法 ===")
bigNum := 1234567890.12345
fmt.Printf("默认: %f\n", bigNum)
fmt.Printf("科学计数法: %e\n", bigNum)
fmt.Printf("更简洁: %E\n", bigNum)
// 3. 时间格式化
fmt.Println("\n=== 时间格式化 ===")
now := time.Now()
fmt.Printf("时间: %s\n", now.Format(time.RFC3339))
fmt.Printf("Unix时间戳: %d\n", now.Unix())
// 4. 构建表格
fmt.Println("\n=== 简易表格 ===")
fmt.Printf("%-10s %-15s %-10s\n", "编号", "姓名", "成绩")
fmt.Println("================================")
fmt.Printf("%-10d %-15s %-10.1f\n", 1, "王五", 95.5)
fmt.Printf("%-10d %-15s %-10.1f\n", 2, "赵六", 88.0)
}
实践练习
练习1:学生信息输出(初级)
创建一个程序,输出以下格式的学生信息:
姓名: 王小明
学号: 2023001
年龄: 20岁
成绩: 88.5分
要求:
- 使用变量存储这些信息
- 使用
Printf格式化输出 - 成绩保留一位小数
预期输出:
姓名: 王小明
学号: 2023001
年龄: 20岁
成绩: 88.5分
练习2:商品价格表(中级)
编写一个程序,打印商品价格表:
商品名称 单价 数量 总价
========================================
苹果 5.50 3 16.50
香蕉 3.20 5 16.00
橘子 4.80 2 9.60
========================================
合计 42.10
要求:
- 使用结构体存储商品信息
- 价格保留两位小数
- 各列要对齐
练习3:日志格式化(高级)
创建一个日志格式化函数 FormatLog,它接受日志级别、消息和时间戳,返回格式化的日志字符串。
函数签名:
func FormatLog(level, message string, timestamp time.Time) string
要求:
- 格式:
[2024-01-15 10:30:45] [INFO] 服务器启动成功 - 日志级别要大写并右对齐(占7个字符宽度)
- 时间格式:
2006-01-02 15:04:05
测试用例:
timestamp := time.Date(2024, 1, 15, 10, 30, 45, 0, time.UTC)
fmt.Println(FormatLog("info", "服务器启动成功", timestamp))
// 输出: [2024-01-15 10:30:45] [ INFO] 服务器启动成功
常见错误
1. 混淆 Println 和 Printf
// 错误示例:使用 Printf 但忘记格式化字符串
fmt.Printf("Hello World") // 输出: Hello World (没有格式化,应该用 Println)
// 正确做法
fmt.Println("Hello World") // 输出: Hello World\n
2. 格式化占位符不匹配
// 错误示例:占位符数量与参数数量不匹配
fmt.Printf("Name: %s, Age: %d\n", "Tom")
// 编译错误:缺少 age 参数
// 正确做法
fmt.Printf("Name: %s, Age: %d\n", "Tom", 25)
3. 字符串连接时忘记类型转换
// 错误示例:直接用 + 连接不同类型
message := "User: " + 123 // 编译错误:不能将 int 与 string 连接
// 正确做法
message := fmt.Sprintf("User: %d", 123)
4. 忽略 Sprintf 的错误返回值
// 错误示例:Sprintf 不会返回错误,但 Fprintf 会
_, err := fmt.Fprintf(os.Stderr, "Error: %s\n", "文件未找到")
if err != nil {
// 处理写入错误
}
5. 过度使用 %v
// 不推荐:总是用 %v 会丢失类型信息
user := User{Name: "Tom", Age: 25}
fmt.Printf("%v\n", user) // {Tom 25} - 看不到字段名
// 推荐:使用 %+v 显示字段名
fmt.Printf("%+v\n", user) // {Name:Tom Age:25}
小结
关键要点
-
三个主要函数:
Println- 简单输出,自动换行Printf- 格式化输出,需要格式字符串Sprintf- 返回格式化后的字符串
-
常用占位符:
%d整数、%s字符串、%f浮点数%T类型、%v默认格式、%+v显示字段名%b二进制、%x十六进制
-
格式化技巧:
- 宽度控制:
%10s(最小宽度)、%-10s(左对齐) - 精度控制:
%.2f(保留两位小数) - 零填充:
%010d(用零填充到10位)
- 宽度控制:
-
最佳实践:
- 优先使用
Sprintf构建字符串,而不是字符串连接 - 复杂格式化时使用
Fprintf输出到特定 writer - 调试时使用
%+v查看结构体详细信息
- 优先使用
-
注意事项:
%本身需要转义:%%显示一个百分号Printf不会自动换行,需要手动添加\n- 格式化字符串中的占位符必须与后续参数数量、类型匹配
下一步学习
在下一课中,我们将学习 strings 包,它提供了丰富的字符串处理函数,与 fmt 包配合使用,可以完成更复杂的文本处理任务。
练习编辑器
go
Loading...