libfmt 是一个现代化的 C++格式化库{fmt}
, 具有以下关键特性:
- 安全性: 受 Python 格式化功能启发,
{fmt}
为printf
系列函数提供安全替代方案. 格式字符串错误在编译时就能被检测出来, 并且通过自动内存管理避免缓冲区溢出错误. - 可扩展性: 默认支持格式化大多数标准类型, 包括容器, 日期和时间等. 例如, 能以类似 JSON 的格式打印
std::vector
. 同时, 用户也能让自定义类型支持格式化, 并进行编译时检查. - 高性能: 在数值格式化方面,
{fmt}
比iostreams
和sprintf
快很多, 速度提升范围从百分之几十到 20 - 30 倍不等. 它尽量减少动态内存分配, 还能将格式字符串编译为优化代码. - Unicode 支持: 在主流操作系统上,
{fmt}
通过UTF-8
和char
字符串提供可移植的 Unicode 支持. 默认情况下与区域设置无关, 但也可选择本地化格式化, 解决了标准库在这方面的一些问题. - 编译速度快: 该库大量使用类型擦除技术来加快编译速度.
fmt/base.h
提供的 API 子集, 包含极少的依赖, 却足以替代所有*printf
的使用场景. 使用{fmt}
的代码编译速度通常比等效的iostreams
代码快几倍, 虽然printf
编译速度更快, 但差距正在缩小. - 二进制体积小: 类型擦除技术还能防止模板膨胀, 使得每次调用生成的二进制代码更紧凑.
- 可移植性强:
{fmt}
代码库独立且小巧, 核心仅由三个头文件组成, 无外部依赖.
本文将介绍libfmt
的安装以及使用教程. 让读者一文了解其功能.
安装教程
-
通过
vcpkg
安装. 如果对 vcpkg 不熟悉, 可以参考我的博客: Vcpkg 使用全攻略: 支持 VS Code, Visual Studio 和 CLionvcpkg install fmt
接着在
CMakeLists.txt
中使用:find_package(fmt CONFIG REQUIRED) target_link_libraries(main.exe PRIVATE fmt::fmt)
-
通过 CMake
FetchContent
方法. 如果您对 CMake 不是很熟悉, 可以参考我的博客: CMake 入门教程: 从基础到实践include(FetchContent)FetchContent_Declare( fmt GIT_REPOSITORY https://github.com/fmtlib/fmt GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1 FetchContent_MakeAvailable(fmt)target_link_libraries(<your-target> fmt::fmt)
-
嵌入使用(Embed). 将
libfmt
仓库加入到自己的工程, 作为一个组件使用.- 先克隆仓库到本地
git clone --depth 1 https://github.com/fmtlib/fmt.git
- 在
CMakeLists.txt
中使用:
add_subdirectory(fmt) # 加到当前的项目中 target_link_libraries(<your-target> fmt::fmt)
使用教程
格式化方式
-
基础样例
fmt 库采用
{}
做占位符. 与 Python 的格式化方式很接近.fmt::println("Hello {} from year {}", "World", 2025); // 输出: Hello World from year 2025
-
指定参数位置
fmt::println("{0}+{0}={1}", 1, 2); // 输出: 1+1=2 fmt::println("{0}{1}{0} {1}{0}{1}", 'a', 'b'); // 输出: aba bab
-
整数格式化
// 数用不同的基数表示整数, 类似printf中的 %d, %o fmt::println("decimal: {0:d}; hex: {0:x}; oct: {0:o}; binary: {0:b}", 42); // 输出: decimal: 42; hex: 2a; oct: 52; binary: 101010// with 0x or 0 or 0b as prefix: fmt::println("decimal: {0:d}; hex: {0:#x}; oct: {0:#o}; binary: {0:#b}", 42); // 输出: decimal: 42; hex: 0x2a; oct: 052; binary: 0b101010// 指定宽度 fmt::println("{:#06x}", 0); // 输出: 0x0000
-
浮点数格式化
fmt::println("{:.{}f}", 3.14, 1); // 输出: 3.1// Replacing % +f, % -f, and % f and specifying a sign : fmt::println("{:+f}; {:+f}", 3.14, -3.14); // 输出: +3.140000; -3.140000// show a space for positive numbers fmt::println("{: f}; {: f}", 3.14, -3.14); // 输出: " 3.140000; -3.140000"// show only the minus -- same as '{:f}; {:f}' fmt::println("{:-f}; {:-f}", 3.14, -3.14); // 输出: 3.140000; -3.140000
-
对齐和宽度
// 左对齐 fmt::println("{:<30}", "left aligned"); // 输出: "left aligned "// 右对齐 fmt::println("{:>30}", "right aligned"); // 输出: " right aligned"// 居中对其, 空格填充 fmt::println("{:^30}", "centered"); // 输出: " centered "// 居中对其, 使用'*'字符对齐 fmt::println("{:*^30}", "centered"); // 使用 '*' 作为填充字符 // 输出: "***********centered***********"// 动态宽度 int width = 30; fmt::println("{:*^{}}", "centered", width); // 输出: "***********centered***********"
-
时间格式化
std::tm datetime = {}; datetime.tm_year = 2025 - 1900; // 年份从 1900 开始计数 datetime.tm_mon = 1 - 1; // 月份从 0 开始计数 datetime.tm_mday = 30; // 日期 datetime.tm_hour = 12; // 小时 datetime.tm_min = 12; // 分钟 datetime.tm_sec = 30; // 秒 fmt::println("time: {:%H:%M}", datetime); // 输出: time: 12:12 fmt::println("datetime: {:%Y-%m-%d %H:%M:%S}", datetime); // 输出: datetime: 2025-01-30 12:12:30
关于时间格式请参考: fmt syntax
-
画 Unicode 字符
fmt::print("┌{0:─^{2}}┐\n""│{1: ^{2}}│\n""└{0:─^{2}}┘\n","", "Hello, world!", 20);
输出:
┌────────────────────┐ │ Hello, world! │ └────────────────────┘
本节的完整代码请参考basic.cpp
打印容器
-
打印标准容器
// vector std::vector<int> vec = {1, 2, 3, 4, 5}; fmt::println("Vector: {}", vec); // 输出: Vector: [1, 2, 3, 4, 5]// array std::array<std::string, 3> arr = {"C++", "Python", "Rust"}; fmt::println("Array: {}", arr); // 输出: Array: [C++, Python, Rust]// map std::map<std::string, int> map = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 20}}; fmt::println("Map: {}", map); // 输出: Map: {"Alice": 25, "Bob": 30, "Charlie": 20}// set std::set<int> set = {3, 1, 4, 1, 5}; fmt::println("Set: {}", set); // 输出: Set: [1, 3, 4, 5]
-
嵌套容器
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; fmt::println("Matrix: {}", matrix); // 输出: Matrix: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
-
格式化组合
std::vector<double> numbers = {3.14159, 2.71828, 1.41421}; fmt::println("Numbers: {::.2f}", numbers); // Numbers: [3.14, 2.72, 1.41]fmt::println("Vector: {::#x}", vec); // Vector: [0x1, 0x2, 0x3, 0x4, 0x5]
本节完整代码请参考: container.cpp
对传统代码的替代
性能对比
下图是官网给出的速度对比, 数值越小表示耗时越少.
替换传统 API
// printf
// 用fmt::printf替换
fmt::fprintf(stderr, "Hello from %s!\n", "fmt::printf");
// 输出: Hello from fmt::printf!// 改写
auto s = fmt::format("Hello from {}!", "fmt::format");
fmt::println(s); // 输出: Hello from fmt::format!// 与ostream的搭配
fmt::println(std::cerr, "Don't {}!", "panic"); // 输出: Don't panic!// 替代 fstream
auto out = fmt::output_file("greeting.txt");
out.print("Hello {}!", "World");// 替代: ostringstream
auto buffer = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buffer), "width:{};", 480);
fmt::format_to(std::back_inserter(buffer), "height:{};", 360);
fmt::println(buffer.data()); // 输出: width:480;height:360;
本节完整代码请参考: stream.cpp
总结
libfmt
作为一个现代的 C++ 格式化库, 其性能强大, 使用方便同时安全性高. 其不少特性已经被引入 C++标准库(容器部分尚未引入). 感兴趣的读者建议下载尝试.
参考链接
- fmt: A modern formatting library
- fmt Github 仓库
源码链接
本文示例源码链接