C++之fmt库介绍和使用(2)
C++之fmt库介绍与使用(2)
Author: Once Day Date: 2025年5月19日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客
参考文章:
- Get Started - {fmt}
- fmtlib/fmt: A modern formatting library
文章目录
- C++之fmt库介绍与使用(2)
- 3. 格式化API
- 3.1 实用工具函数
- 3.2 系统错误
- 3.3 自定义分配器
- 3.4 区域设置(Locale)
- 3.5 旧版编译时检查
- 4. 其他特性
- 4.1 范围和元组格式化
- 4.2 日期和时间格式化
- 4.3 终端颜色和文本样式
- 4.4 动态参数列表
3. 格式化API
fmt/format.h
定义了完整的格式化 API,提供额外的格式化函数和区域设置支持。
template <typename... T>
auto format(format_string<T...> fmt, T&&... args) -> std::string;#include <fmt/format.h>
std::string message = fmt::format("The answer is {}.", 42);
根据 fmt
中的规范格式化参数 args
,并将结果作为字符串返回。
auto vformat(string_view fmt, format_args args) -> std::string;template <detail::fixed_string S>
constexpr auto operator""_a();
3.1 实用工具函数
template <typename T>
auto ptr(T p) -> const void*;auto s = fmt::format("{}", fmt::ptr(p));
将指针 p
转换为 const void*
,用于指针格式化。
template <typename Enum>
constexpr auto underlying(Enum e) -> underlying_t<Enum>;enum class color { red, green, blue };
auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0"
将枚举值 e
转换为其底层类型。
template <typename T>
auto to_string(const T& value) -> std::string;
将任意对象转换为字符串std::string对象。
template <typename T>
auto group_digits(T value) -> group_digits_view<T>;fmt::print("{}", fmt::group_digits(12345));
// Output: "12,345"
返回一个视图,使用与区域设置无关的逗号 ,
作为千位分隔符格式化整数值。
template <typename T>
class detail::buffer;constexpr auto size() -> size_t; //返回缓冲区的大小。
constexpr auto capacity() -> size_t; // 返回缓冲区的容量。
auto data() -> T*; // 返回指向缓冲区数据的指针(无空终止符)。
void clear(); // 清空缓冲区。
void append(const U* begin, const U* end); // 将数据追加到缓冲区末尾。
具有可选增长能力的连续内存缓冲区。它是内部类,不应直接使用,需通过 memory_buffer
使用。
template <typename T, size_t SIZE, typename Allocator>
class basic_memory_buffer;// 这会将 "The answer is 42." 追加到 out 中。缓冲区内容可通过 to_string(out) 转换为 std::string。
auto out = fmt::memory_buffer();
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);// 通过移动构造函数从另一个对象转移内容。
basic_memory_buffer(basic_memory_buffer&& other);// 通过移动赋值运算符从另一个对象转移内容。
auto operator=(basic_memory_buffer&& other) -> basic_memory_buffer&;// 调整缓冲区大小以容纳 count 个元素。如果 T 是 POD 类型,新元素可能未初始化。
void resize(size_t count);// 将缓冲区容量增加到 new_capacity。
void reserve(size_t new_capacity);
为平凡可复制 / 构造的类型提供的动态增长内存缓冲区,前 SIZE
个元素存储在对象自身中。最常见的用法是通过 char
类型的 memory_buffer
别名。
3.2 系统错误
{fmt} 库不使用 errno
向用户传递错误信息,但可能会调用设置 errno
的系统函数。用户不应假设库函数会保留 errno
的值。
template <typename... T>
auto system_error(int error_code, format_string<T...> fmt, T&&... args) -> std::system_error;// 抛出 std::system_error,错误描述为 "cannot open file 'madeup': No such file or directory"(系统消息可能因环境而异)。
const char* filename = "madeup";
FILE* file = fopen(filename, "r");
if (!file)throw fmt::system_error(errno, "cannot open file '{}'", filename);
使用 fmt::format(fmt, args...)
格式化错误消息,构造 std::system_error
对象。error_code
是 errno
提供的系统错误码。
void format_system_error(detail::buffer<char>& out, int error_code, const char* message);
为操作系统或语言运行时返回的错误(如文件打开错误)格式化错误消息,并写入 out
。格式与 std::system_error(ec, message)
一致(其中 ec
是 std::error_code(error_code, std::generic_category())
)。实现可能不同,但通常格式为:
<message>: <system-message>
其中 <message>
是传入的消息,<system-message>
是与错误码对应的系统消息。error_code
是 errno
提供的系统错误码。
3.3 自定义分配器
{fmt} 库支持自定义动态内存分配器。自定义分配器类可作为模板参数指定给 fmt::basic_memory_buffer
:
using custom_memory_buffer = fmt::basic_memory_buffer<char, fmt::inline_buffer_size, custom_allocator>;
也可以编写使用自定义分配器的格式化函数:
using custom_string =std::basic_string<char, std::char_traits<char>, custom_allocator>;auto vformat(custom_allocator alloc, fmt::string_view fmt,fmt::format_args args) -> custom_string {auto buf = custom_memory_buffer(alloc);fmt::vformat_to(std::back_inserter(buf), fmt, args);return custom_string(buf.data(), buf.size(), alloc);
}template <typename ...Args>
auto format(custom_allocator alloc, fmt::string_view fmt,const Args& ... args) -> custom_string {return vformat(alloc, fmt, fmt::make_format_args(args...));
}
分配器仅用于输出容器。格式化函数通常不会为内置类型和字符串类型执行任何分配操作,除非是非默认的浮点格式化(偶尔会回退到 sprintf
)。
3.4 区域设置(Locale)
默认情况下,所有格式化操作均与区域设置无关。使用 'L'
格式说明符可插入区域设置中的合适数字分隔符:
#include <fmt/core.h>
#include <locale>std::locale::global(std::locale("en_US.UTF-8"));
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
fmt/format.h
提供以下接受 std::locale
作为参数的格式化函数重载。locale
类型作为模板参数,避免引入开销较大的 <locale>
头文件:
template <typename... T>
auto format(detail::locale_ref loc, format_string<T...> fmt, T&&... args) -> std::string;template <typename OutputIt, typename... T>
auto format_to(OutputIt out, detail::locale_ref loc, format_string<T...> fmt, T&&... args) -> OutputIt;template <typename... T>
auto formatted_size(detail::locale_ref loc, format_string<T...> fmt, T&&... args) -> size_t;
3.5 旧版编译时检查
FMT_STRING
宏用于在旧版编译器上启用编译时检查,要求 C++14 或更高版本,在 C++11 中为无操作(no-op)。
FMT_STRING(s)
// 编译时错误:'d' 是字符串的无效说明符
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
从字符串字面量 s
构造旧版编译时格式字符串。
若要强制使用旧版编译时检查,定义预处理变量 FMT_ENFORCE_COMPILE_STRING
。设置后,接受 FMT_STRING
的函数将无法使用常规字符串编译。
4. 其他特性
4.1 范围和元组格式化
fmt/ranges.h
为范围(ranges)和元组(tuples)提供格式化支持:
#include <fmt/ranges.h>fmt::print("{}", std::tuple<char, int>{'a', 42});
// 输出:('a', 42)
通过 fmt::join
,可以使用自定义分隔符分隔元组元素:
#include <fmt/ranges.h>auto t = std::tuple<int, char>{1, 'a'};
fmt::print("{}", fmt::join(t, ", "));
// 输出:1, a
template <typename Range>
auto join(Range&& r, string_view sep) -> join_view<decltype(detail::range_begin(r)), decltype(detail::range_end(r))>;auto v = std::vector<int>{1, 2, 3};
fmt::print("{}", fmt::join(v, ", "));
// 输出:1, 2, 3// fmt::join 会将传递的格式说明符应用于范围元素:
fmt::print("{:02}", fmt::join(v, ", "));
// 输出:01, 02, 03
返回一个视图,使用 sep
分隔范围元素进行格式化。
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>;
返回一个视图,格式化迭代器范围 [begin, end)
,元素用 sep
分隔。
template <typename T>
auto join(std::initializer_list<T> list, string_view sep) -> join_view<const T*, const T*>;// 输出:"1, 2, 3"
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
返回一个对象,格式化 std::initializer_list
,元素用 sep
分隔。
4.2 日期和时间格式化
fmt/chrono.h
为以下类型提供格式化器:
std::chrono::duration
std::chrono::time_point
std::tm
格式语法请参考 Chrono 格式规范。
#include <fmt/chrono.h>int main() {std::time_t t = std::time(nullptr);fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));// 输出:The date is 2020-11-07.// (2020-11-07 为当前日期)using namespace std::literals::chrono_literals;fmt::print("Default format: {} {}\n", 42s, 100ms);// 输出:Default format: 42s 100msfmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);// 输出:strftime-like format: 03:15:30
}
将 std::time_t
表示的自 epoch 以来的时间转换为本地时间的日历时间(std::tm
)。与 std::localtime
不同,该函数在大多数平台上是线程安全的。
auto localtime(std::time_t time) -> std::tm;
将 std::time_t
表示的自 epoch 以来的时间转换为协调世界时(UTC)的日历时间(std::tm
)。与 std::gmtime
不同,该函数在大多数平台上是线程安全的。
auto gmtime(std::time_t time) -> std::tm;
4.3 终端颜色和文本样式
fmt/color.h
提供终端颜色和文本样式输出支持。
template <typename... T>
void print(const text_style& ts, format_string<T...> fmt, T&&... args);fmt::print(fmt::emphasis::bold | fg(fmt::color::red),"Elapsed time: {0:.2f} seconds", 1.23);
使用 ANSI 转义序列指定文本格式,格式化字符串并打印到标准输出(stdout
)。
auto fg(detail::color_type foreground) -> text_style;
从前景色(文本颜色)创建文本样式。
auto bg(detail::color_type background) -> text_style;
从背景色创建文本样式。
template <typename T>
auto styled(const T& value, text_style ts) -> detail::styled_arg<remove_cvref_t<T>>;
返回一个使用 ANSI 转义序列格式化的参数,用于格式化函数。
fmt::print("Elapsed time: {0:.2f} seconds",fmt::styled(1.23, fmt::fg(fmt::color::green) |fmt::bg(fmt::color::blue)));
4.4 动态参数列表
头文件 fmt/args.h
提供了 dynamic_format_arg_store
,这是一种类似构建器的 API,用于动态构造格式化参数列表。
template <typename Context>
class dynamic_format_arg_store;
功能:带存储功能的动态格式化参数列表,可隐式转换为 fmt::basic_format_args
,用于传递给类型擦除的格式化函数(如 fmt::vformat
)。
void push_back(const T& arg);fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42); // 整数
store.push_back("abc"); // 字符串(复制存储)
store.push_back(1.5f); // 浮点数
std::string result = fmt::vformat("{} and {} and {}", store);
// 输出:"42 and abc and 1.5"
作用:向动态存储中添加参数,供后续格式化函数使用。
注意:自定义类型和字符串类型(非字符串视图)会被复制到存储中,必要时动态分配内存。
void push_back(std::reference_wrapper<T> arg);fmt::dynamic_format_arg_store<fmt::format_context> store;
char band[] = "Rolling Stones";
store.push_back(std::cref(band)); // 存储引用
band[9] = 'c'; // 修改原始数组
std::string result = fmt::vformat("{}", store);
// 输出:"Rolling Scones"(引用值随原始数据变化)
作用:向动态存储中添加参数的引用(避免复制)。
void push_back(const detail::named_arg<char_type, T>& arg);
作用:添加命名参数(支持 std::reference_wrapper
避免参数复制,但名称始终会被复制到存储中)。
void clear();
作用:清空存储中的所有参数。
void reserve(size_t new_cap, size_t new_cap_named);
作用:预留存储空间,至少容纳 new_cap
个参数(包括 new_cap_named
个命名参数)。
size_t size();
作用:返回存储中的参数数量。