fmt 库的核心优势是 用法简洁、类型安全、兼容多场景,以下是从基础到进阶的完整使用指南,涵盖常用功能和关键场景。
一、前置准备:环境配置
使用前需确保 fmt 库已集成到项目中
- 编译依赖:链接 fmt 库(CMake 项目直接链接
fmt::fmt目标,非 CMake 项目需手动添加头文件和src/format.cc编译)。 - 头文件引入:核心功能只需包含主头文件:
#include <fmt/format.h> // 核心格式化功能(fmt::format、fmt::print 等) #include <fmt/ranges.h> // 容器格式化(可选,如 vector、map) #include <fmt/chrono.h> // 时间格式化(可选,如 std::chrono)
二、核心用法:格式化输出与字符串构建
fmt 的核心 API 是 fmt::format(构建字符串)和 fmt::print(直接输出),语法兼容 Python 格式化风格,比 printf 更简洁,比 iostreams 更高效。
1. 基础格式化(类似 printf,但更安全)
支持位置参数、类型自动推导,无需手动指定格式符(如 %d %s),错误会在编译时捕获。
#include <fmt/format.h>
#include <iostream>
int main() {
// 1. 基础类型格式化(int、string、float 等)
std::string msg1 = fmt::format("整数:{},字符串:{},浮点数:{:.2f}", 42, "hello", 3.1415);
std::cout << msg1 << std::endl; // 输出:整数:42,字符串:hello,浮点数:3.14
// 2. 位置指定(解决参数顺序问题)
std::string msg2 = fmt::format("第2个参数:{1},第1个参数:{0}", "A", "B");
std::cout << msg2 << std::endl; // 输出:第2个参数:B,第1个参数:A
// 3. 直接输出到控制台(无需手动拼接 cout)
fmt::print("直接输出:{} + {} = {}\n", 10, 20, 30); // 输出:直接输出:10 + 20 = 30
// 4. 输出到文件(支持 FILE* 或 std::ostream)
fmt::print(stdout, "输出到标准输出\n");
fmt::print(stderr, "输出到标准错误\n");
return 0;
}
2. 容器格式化(开箱即用,无需手动遍历)
包含 <fmt/ranges.h> 后,支持 std::vector、std::array、std::map 等容器直接格式化,默认输出类似 JSON 风格。
#include <fmt/ranges.h>
#include <vector>
#include <map>
int main() {
std::vector<int> nums = {1, 2, 3, 4};
fmt::print("向量:{}\n", nums); // 输出:向量:[1, 2, 3, 4]
std::map<std::string, int> score = {{"Alice", 95}, {"Bob", 88}};
fmt::print("字典:{}\n", score); // 输出:字典:{"Alice": 95, "Bob": 88}
// 自定义分隔符(通过格式说明符)
fmt::print("向量(空格分隔):{:v}\n", fmt::join(nums, " ")); // 输出:1 2 3 4
return 0;
}
3. 时间格式化(兼容 std::chrono)
包含 <fmt/chrono.h> 后,支持时间点、时间段的格式化,无需手动转换 time_t。
#include <fmt/chrono.h>
#include <chrono>
int main() {
// 格式化当前时间(本地时间)
auto now = std::chrono::system_clock::now();
fmt::print("当前时间:{:%Y-%m-%d %H:%M:%S}\n", now); // 输出:当前时间:2024-10-01 14:30:00
// 格式化时间段
std::chrono::duration<double> diff = 2.5h + 30min;
fmt::print("时长:{}\n", diff); // 输出:时长:2h30m0s
fmt::print("时长(秒):{:.1f}s\n", diff); // 输出:时长(秒):9000.0s
return 0;
}
三、进阶用法:自定义类型与高级特性
1. 自定义类型格式化(可扩展性核心)
通过实现 fmt::formatter 模板特化,让自定义类支持 fmt 格式化,还能兼容编译时检查。
#include <fmt/format.h>
// 自定义类
struct Point {
int x, y;
};
// 为 Point 实现 formatter 特化
template <>
struct fmt::formatter<Point> {
// 解析格式说明符(可选,如 {:x} 控制输出格式)
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.end(); // 此处不处理自定义格式符,直接返回结束迭代器
}
// 格式化逻辑
template <typename FormatContext>
auto format(const Point& p, FormatContext& ctx) -> decltype(ctx.out()) {
// 拼接输出:(x, y)
return fmt::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
Point p = {10, 20};
fmt::print("坐标:{}\n", p); // 输出:坐标:(10, 20)
return 0;
}
2. 避免缓冲区溢出:安全写入
fmt::format_to_n 可限制输出长度,防止缓冲区溢出,返回实际写入的字符数和是否截断的标记。
#include <fmt/format.h>
#include <array>
int main() {
std::array<char, 10> buf; // 固定大小缓冲区(最多存 9 个字符 + 终止符)
auto [size, truncated] = fmt::format_to_n(buf.data(), buf.size() - 1, "Hello {}", "World");
buf[size] = '\0'; // 手动添加字符串终止符
fmt::print("结果:{},是否截断:{}\n", buf.data(), truncated); // 输出:结果:Hello W,是否截断:true
return 0;
}
3. 动态参数(运行时确定参数个数)
当参数个数不确定时,使用 fmt::dynamic_format_arg_store 动态添加参数,适合日志、配置等场景。
#include <fmt/format.h>
#include <vector>
int main() {
fmt::dynamic_format_arg_store<fmt::format_context> args;
args.push_back("name");
args.push_back("Alice");
args.push_back("age");
args.push_back(25);
// 动态参数格式化
std::string msg = fmt::vformat("{}: {}, {}: {}", args);
fmt::print("{}\n", msg); // 输出:name: Alice, age: 25
return 0;
}
四、特殊场景:与 C 标准库 / STL 兼容
1. 兼容 printf 格式符
如果习惯 printf 的格式符(如 %04d %x),fmt 完全支持,同时保留类型安全。
#include <fmt/format.h>
int main() {
// 用 printf 格式符格式化
fmt::print("十六进制:{:x}\n", 255); // 输出:ff
fmt::print("补零对齐:{:04d}\n", 42); // 输出:0042
fmt::print("科学计数法:{:e}\n", 123.4); // 输出:1.234000e+02
return 0;
}
2. 格式化 STL 字符串与流
支持 std::string、std::wstring,也可输出到 std::ostream(如 std::cout)。
#include <fmt/format.h>
#include <string>
#include <iostream>
int main() {
std::string name = "Bob";
// 格式化 std::string
std::string msg = fmt::format("Hello, {}", name);
std::cout << msg << std::endl; // 输出:Hello, Bob
// 直接输出到 std::ostream
fmt::print(std::cout, "Width: {}\n", 100); // 等价于 std::cout << "Width: 100" << std::endl
return 0;
}
五、常见问题与注意事项
- 编译错误:若使用容器 / 时间格式化时报错,检查是否包含对应的头文件(
<fmt/ranges.h><fmt/chrono.h>)。 - C++ 版本要求:核心功能支持 C++11 及以上,部分高级特性(如编译时格式检查)需 C++17+。
- 性能优化:频繁格式化场景可使用
fmt::memory_buffer复用缓冲区,减少内存分配:fmt::memory_buffer buf; fmt::format_to(buf, "First: {}", 1); fmt::format_to(buf, ", Second: {}", 2); std::string result = fmt::to_string(buf); // 结果:"First: 1, Second: 2"
总结
fmt 库的使用核心是 “简洁 + 安全”:基础场景用 fmt::format/fmt::print 替代 printf/iostreams,进阶场景(自定义类型、动态参数)通过扩展 formatter 或 dynamic_format_arg_store 实现,同时保持高性能和跨平台兼容性。













