C++之fmt库介绍和使用(3)
C++之fmt库格式化语法(3)
Author: Once Day Date: 2025年5月20日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客
参考文章:
- Get Started - {fmt}
- fmtlib/fmt: A modern formatting library
- Syntax - {fmt}
文章目录
- C++之fmt库格式化语法(3)
- 1. 格式字符串语法
- 2. 格式说明符迷你语言
- 3. 时间格式说明符
- 4. 范围格式说明符
- 5. 格式示例
1. 格式字符串语法
fmt::format
和 fmt::print
等格式化函数使用本节所述的相同格式字符串语法。
格式字符串包含由大括号 {}
包围的 “替换字段”。未包含在括号内的任何内容均视为文本字面量,这些内容将原样复制到输出中。如果需要在文本字面量中包含大括号字符,可以通过双写来转义:{{
和 }}
。
替换字段的语法如下:
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
arg_id ::= integer | identifier
integer ::= digit+
digit ::= "0"..."9"
identifier ::= id_start id_continue*
id_start ::= "a"..."z" | "A"..."Z" | "_"
id_continue ::= id_start | digit
通俗地说,替换字段可以以 arg_id
开头,该 arg_id
指定要格式化其值并将其插入输出以替换该字段的参数。arg_id
后面可选跟随一个格式说明符(format_spec
),该说明符以冒号 :
开头。这些说明符为替换值指定非默认的格式。
如果格式字符串中的数字 arg_id
按顺序为 0
、1
、2
……,则可以全部省略(而不仅仅是部分省略),并且数字 0
、1
、2
…… 将按该顺序自动插入。
命名参数可以通过其名称或索引引用。
以下是一些简单的格式字符串示例:
"First, thou shalt count to {0}" // 引用第一个参数
"Bring me a {}" // 隐式引用第一个参数
"From {} to {}" // 等同于 "From {0} to {1}"
format_spec
字段包含有关如何呈现值的规范,包括字段宽度、对齐方式、填充、小数精度等细节。每个值类型可以定义自己的 “格式化迷你语言” 或对 format_spec
的解释。
大多数内置类型支持通用的格式化迷你语言,下一节将对此进行描述。
在某些位置,format_spec
字段还可以包含嵌套的替换字段。这些嵌套的替换字段只能包含参数 ID,不允许使用格式说明符。这使得值的格式化可以动态指定。
2. 格式说明符迷你语言
“格式说明符” 用于格式字符串中的替换字段内,以定义如何呈现单个值。每个可格式化类型可能会定义如何解释格式说明符。
大多数内置类型实现了以下格式说明符选项,尽管某些格式化选项仅由数值类型支持。
标准格式说明符的一般形式为:
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
fill ::= <除'{'或'}'之外的任意字符>
align ::= "<" | ">" | "^"
sign ::= "+" | "-" | " "
width ::= integer | "{" [arg_id] "}"
precision ::= integer | "{" [arg_id] "}"
type ::= "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" |"g" | "G" | "o" | "p" | "s" | "x" | "X" | "?"
填充字符可以是除 {
或 }
之外的任何 Unicode 代码点。填充字符的存在由紧随其后的字符表示,该字符必须是对齐选项之一。如果 format_spec
的第二个字符不是有效的对齐选项,则假定填充字符和对齐选项均不存在。
各种对齐选项的含义如下:
选项 | 含义 |
---|---|
< | 强制字段在可用空间内左对齐(这是大多数对象的默认设置)。 |
> | 强制字段在可用空间内右对齐(这是数字的默认设置)。 |
^ | 强制字段在可用空间内居中对齐。 |
请注意,除非定义了最小字段宽度,否则字段宽度将始终与填充数据的大小相同,因此在这种情况下对齐选项没有意义。
符号选项仅对浮点数和有符号整数类型有效,可以是以下之一:
选项 | 含义 |
---|---|
+ | 指示正数和负数都应使用符号。 |
- | 指示仅负数使用符号(这是默认行为)。 |
空格 | 指示正数前使用前导空格,负数前使用减号。 |
#
选项会为转换启用 “替代形式”。替代形式的定义因类型而异。此选项仅对整数和浮点类型有效。对于整数,当使用二进制、八进制或十六进制输出时,此选项会在输出值前添加相应的前缀 "0b"
(或 "0B"
)、"0"
或 "0x"
(或 "0X"
)。前缀是小写还是大写由类型说明符的大小写决定,例如,类型 'x'
使用前缀 "0x"
,而 'X'
使用 "0X"
。对于浮点数,替代形式会使转换结果始终包含小数点字符,即使其后没有数字。通常,只有当小数点后有数字时,这些转换结果中才会出现小数点字符。此外,对于 'g'
和 'G'
转换,结果中的尾随零不会被删除。
width
是一个十进制整数,定义了最小字段宽度。如果未指定,则字段宽度将由内容决定。
在宽度字段前加上零字符 '0'
可为数值类型启用符号感知的零填充。它强制填充位于符号或基数(如果有)之后但数字之前。这用于以 "+000000120"
的形式打印字段。此选项仅对数值类型有效,对无穷大和 NaN 的格式化无效。当存在任何对齐说明符时,此选项将被忽略。
precision
是一个十进制数,指示使用 'f'
和 'F'
格式化的浮点值的小数点后应显示多少位,或使用 'g'
或 'G'
格式化的浮点值的小数点前后应显示多少位。对于非数字类型,该字段指示最大字段大小,即从字段内容中使用的字符数。整数、字符、布尔值和指针值不允许使用精度。请注意,即使指定了精度,C 字符串也必须以空字符结尾。
'L'
选项使用当前区域设置插入适当的数字分隔符字符。此选项仅对数值类型有效。
最后,type
确定数据的呈现方式。
可用的字符串呈现类型:
类型 | 含义 |
---|---|
's' | 字符串格式。这是字符串的默认类型,可以省略。 |
'?' | 调试格式。字符串会被引号引起来,特殊字符会被转义。 |
无 | 与 's' 相同。 |
可用的字符呈现类型:
类型 | 含义 |
---|---|
'c' | 字符格式。这是字符的默认类型,可以省略。 |
'?' | 调试格式。字符会被引号引起来,特殊字符会被转义。 |
无 | 与 'c' 相同。 |
可用的整数呈现类型:
类型 | 含义 |
---|---|
'b' | 二进制格式。以基数 2 输出数字。使用 '#' 选项时会在输出值前添加前缀 "0b" 。 |
'B' | 二进制格式。以基数 2 输出数字。使用 '#' 选项时会在输出值前添加前缀 "0B" 。 |
'c' | 字符格式。将数字作为字符输出。 |
'd' | 十进制整数。以基数 10 输出数字。 |
'o' | 八进制格式。以基数 8 输出数字。 |
'x' | 十六进制格式。以基数 16 输出数字,使用小写字母表示 9 以上的数字。使用 '#' 选项时会在输出值前添加前缀 "0x" 。 |
'X' | 十六进制格式。以基数 16 输出数字,使用大写字母表示 9 以上的数字。使用 '#' 选项时会在输出值前添加前缀 "0X" 。 |
无 | 与 'd' 相同。 |
整数呈现类型也可用于字符和布尔值,但 'c'
不能用于布尔值。如果未指定呈现类型,布尔值将使用文本表示形式(true
或 false
)进行格式化。
可用的浮点数呈现类型:
类型 | 含义 |
---|---|
'a' | 十六进制浮点格式。以基数 16 输出数字,前缀为 "0x" ,使用小写字母表示 9 以上的数字。使用 'p' 表示指数。 |
'A' | 与 'a' 相同,但前缀、9 以上的数字和指数使用大写字母。 |
'e' | 指数表示法。使用字母 'e' 表示指数,以科学记数法输出数字。 |
'E' | 指数表示法。与 'e' 相同,但使用大写字母 'E' 作为分隔符。 |
'f' | 定点表示法。将数字显示为定点数。 |
'F' | 定点表示法。与 'f' 相同,但将 nan 转换为 NAN ,将 inf 转换为 INF 。 |
'g' | 通用格式。对于给定的精度 p >= 1 ,此选项将数字四舍五入到 p 位有效数字,然后根据其大小以定点格式或科学记数法格式化结果。精度为 0 时视为等同于精度为 1。 |
'G' | 通用格式。与 'g' 相同,但当数字太大时切换到 'E' 。无穷大和 NaN 的表示也使用大写。 |
无 | 类似于 'g' ,但默认精度足够高,能表示特定值。 |
可用的指针呈现类型:
类型 | 含义 |
---|---|
'p' | 指针格式。这是指针的默认类型,可以省略。 |
无 | 与 'p' 相同。 |
3. 时间格式说明符
适用于 chrono
持续时间、时间点类型以及 std::tm
的格式说明符具有以下语法:
chrono_format_spec ::= [[fill]align][width]["." precision][chrono_specs]
chrono_specs ::= conversion_spec |chrono_specs (conversion_spec | literal_char)
conversion_spec ::= "%" [padding_modifier] [locale_modifier] chrono_type
literal_char ::= <除'{', '}'或'%'之外的任意字符>
padding_modifier ::= "-" | "_" | "0"
locale_modifier ::= "E" | "O"
chrono_type ::= "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" |"F" | "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" |"n" | "p" | "q" | "Q" | "r" | "R" | "S" | "t" | "T" |"u" | "U" | "V" | "w" | "W" | "x" | "X" | "y" | "Y" |"z" | "Z" | "%"
文本字面量字符()literal_char
)会原样复制到输出中。精度(precision
)仅对具有浮点表示类型的 std::chrono::duration
类型有效。
可用呈现类型(chrono_type):
类型 | 含义 |
---|---|
'a' | 星期几的缩写名称(如 “Sat”)。若值不包含有效星期,抛出 format_error 异常。 |
'A' | 星期几的完整名称(如 “Saturday”)。若值不包含有效星期,抛出 format_error 异常。 |
'b' | 月份的缩写名称(如 “Nov”)。若值不包含有效月份,抛出 format_error 异常。 |
'B' | 月份的完整名称(如 “November”)。若值不包含有效月份,抛出 format_error 异常。 |
'c' | 日期和时间表示(如 “Sat Nov 12 22:04:00 1955”)。修饰命令 %Ec 生成区域设置的备用日期时间表示。 |
'C' | 年份除以 100 的结果(向下取整,如 “19”)。若结果为个位数,前缀补 0。修饰命令 %EC 生成区域设置的世纪替代表示。 |
'd' | 月份中的日期(十进制数,如 “02”)。若结果为个位数,前缀补 0。修饰命令 %Od 生成区域设置的替代表示。 |
'D' | 等效于 %m/%d/%y (如 “11/12/55”)。 |
'e' | 月份中的日期(十进制数,如 “2”)。若结果为个位数,前缀补空格。修饰命令 %Oe 生成区域设置的替代表示。 |
'F' | 等效于 %Y-%m-%d (如 “1955-11-12”)。 |
'g' | ISO 周制年份的最后两位数字。若结果为个位数,前缀补 0。 |
'G' | ISO 周制年份(十进制数,不足四位左补 0 至四位)。 |
'h' | 等效于 %b (如 “Nov”)。 |
'H' | 小时(24 小时制,如 “22”)。若结果为个位数,前缀补 0。修饰命令 %OH 生成区域设置的替代表示。 |
'I' | 小时(12 小时制,如 “10”)。若结果为个位数,前缀补 0。修饰命令 %OI 生成区域设置的替代表示。 |
'j' | 若格式化类型为 duration 特化,输出无填充的天数(十进制);否则为一年中的第几天(如 “001”,1 月 1 日为 001,不足三位左补 0)。 |
'm' | 月份(十进制数,1 月为 01)。若结果为个位数,前缀补 0。修饰命令 %Om 生成区域设置的替代表示。 |
'M' | 分钟(十进制数)。若结果为个位数,前缀补 0。修饰命令 %OM 生成区域设置的替代表示。 |
'n' | 换行符。 |
'p' | 12 小时制的 AM/PM 标识。 |
'q' | 持续时间的单位后缀(如 “s” 表示秒)。 |
'Q' | 持续时间的数值(等效于通过 .count() 提取)。 |
'r' | 12 小时制时间(如 “10:04:00 PM”)。 |
'R' | 等效于 %H:%M (如 “22:04”)。 |
'S' | 秒(十进制数,不足 10 秒前缀补 0)。若输入精度无法精确表示为秒,则格式化为固定格式的十进制浮点数,精度与输入匹配(若转换为十进制秒的小数部分超过 18 位,则使用微秒精度)。修饰命令 %OS 生成区域设置的替代表示。 |
't' | 水平制表符。 |
'T' | 等效于 %H:%M:%S 。 |
'u' | ISO 星期数(1-7,星期一为 1)。修饰命令 %Ou 生成区域设置的替代表示。 |
'U' | 一年中的周数(十进制数,当年第一个星期日为第 01 周第一天,之前为第 00 周,不足两位前缀补 0)。修饰命令 %OU 生成区域设置的替代表示。 |
'V' | ISO 周制周数(十进制数,不足两位前缀补 0)。修饰命令 %OV 生成区域设置的替代表示。 |
'w' | 星期数(0-6,星期日为 0)。修饰命令 %Ow 生成区域设置的替代表示。 |
'W' | 一年中的周数(十进制数,当年第一个星期一为第 01 周第一天,之前为第 00 周,不足两位前缀补 0)。修饰命令 %OW 生成区域设置的替代表示。 |
'x' | 日期表示(如 “11/12/55”)。修饰命令 %Ex 生成区域设置的备用日期表示。 |
'X' | 时间表示(如 “10:04:00”)。修饰命令 %EX 生成区域设置的备用时间表示。 |
'y' | 年份的最后两位数字(不足两位前缀补 0)。修饰命令 %Oy 生成区域设置的替代表示;%Ey 生成相对于 %EC 的年份偏移替代表示。 |
'Y' | 完整年份(十进制数,不足四位左补 0 至四位)。修饰命令 %EY 生成区域设置的完整年份替代表示。 |
'z' | UTC 偏移量(ISO 8601:2004 格式,如 -0430 表示晚于 UTC 4 小时 30 分钟;零偏移为 +0000 )。修饰命令 %Ez 和 %Oz 在小时和分钟间插入 : (如 -04:30 )。若偏移信息不可用,抛出 format_error 异常。 |
'Z' | 时区缩写。若时区缩写不可用,抛出 format_error 异常。 |
'%' | 字面量 % 字符。 |
日历相关说明符(如 'd'
表示日期)仅适用于 std::tm
和时间点类型,不适用于持续时间(duration
)。
填充修饰符(padding_modifier
):
类型 | 含义 |
---|---|
'_' | 用空格填充数值结果。 |
'-' | 不填充数值结果字符串。 |
'0' | 用零填充数值结果字符串。 |
支持的呈现类型:仅适用于 'H'
, 'I'
, 'M'
, 'S'
, 'U'
, 'V'
, 'W'
, 'Y'
, 'd'
, 'j'
, 'm'
。
4. 范围格式说明符
适用于范围类型的格式说明符具有以下语法:
range_format_spec ::= ["n"][range_type][range_underlying_spec]
n
选项:格式化范围时不包含开始和结束括号([]
)。
可用呈现类型(range_type):
类型 | 含义 |
---|---|
无 | 默认格式:以 [元素1, 元素2, ...] 的形式输出范围元素。 |
's' | 字符串格式:将范围格式化为字符串(要求元素类型为字符类型)。 |
'?s' | 调试格式:将范围格式化为转义字符串(要求元素类型为字符类型)。 |
当 range_type
为 's'
或 '?s'
时,范围元素类型必须是字符类型(如 char
、wchar_t
等)。
'n'
选项、range_underlying_spec
与 's'
/'?s'
互斥,不可同时使用。
range_underlying_spec
根据范围元素类型的格式化器解析该说明符,用于自定义元素的呈现方式。
- 默认行为:字符或字符串范围会被转义并添加引号(例如
['h', 'e']
)。 - 自定义行为:若提供
range_underlying_spec
(即使为空),则按指定格式输出元素(例如禁用转义或使用特定数值格式)。
// 输出默认格式(包含括号)
fmt::print("{}", std::vector{10, 20, 30});
// 输出:[10, 20, 30]// 使用十六进制格式(带前缀 #x)
fmt::print("{::#x}", std::vector{10, 20, 30});
// 输出:[0xa, 0x14, 0x1e]// 字符范围默认带单引号和括号
fmt::print("{}", std::vector{'h', 'e', 'l', 'l', 'o'});
// 输出:['h', 'e', 'l', 'l', 'o']// 省略括号(n 选项)
fmt::print("{:n}", std::vector{'h', 'e', 'l', 'l', 'o'});
// 输出:'h', 'e', 'l', 'l', 'o'// 格式化为字符串(s 选项,元素需为字符类型)
fmt::print("{:s}", std::vector{'h', 'e', 'l', 'l', 'o'});
// 输出:"hello"// 格式化为转义字符串(?s 选项,支持特殊字符转义)
fmt::print("{:?s}", std::vector{'h', 'e', 'l', 'l', 'o', '\n'});
// 输出:"hello\n"// 元素按默认格式呈现(不带引号)
fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// 输出:[h, e, l, l, o]// 元素按十进制整数格式呈现(d 类型)
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// 输出:[104, 101, 108, 108, 111]
5. 格式示例
本节包含格式语法的示例,并与 printf
格式化进行对比。
在大多数情况下,该语法与 printf
格式化类似,但新增了 {}
符号,并使用 :
替代 %
。例如,"%03.2f"
可转换为 "{:03.2f}"
。
新的格式语法还支持以下示例中展示的新选项和不同选项。
按位置访问参数:
fmt::format("{0}, {1}, {2}", 'a', 'b', 'c');
// 结果: "a, b, c"fmt::format("{}, {}, {}", 'a', 'b', 'c');
// 结果: "a, b, c" (隐式按顺序引用参数)fmt::format("{2}, {1}, {0}", 'a', 'b', 'c');
// 结果: "c, b, a" (逆序引用参数)fmt::format("{0}{1}{0}", "abra", "cad"); // 参数索引可重复使用
// 结果: "abracadabra" ("abra" 重复出现两次)
文本对齐与指定宽度:
fmt::format("{:<30}", "left aligned");
// 结果: "left aligned " (左对齐,填充空格)fmt::format("{:>30}", "right aligned");
// 结果: " right aligned" (右对齐,填充空格)fmt::format("{:^30}", "centered");
// 结果: " centered " (居中对齐,填充空格)fmt::format("{:*^30}", "centered"); // 使用 '*' 作为填充字符
// 结果: "***********centered***********" (居中对齐,填充星号)
动态宽度:
fmt::format("{:<{}}", "left aligned", 30);
// 结果: "left aligned " (第二个参数指定宽度为 30)
动态精度:
fmt::format("{:.{}f}", 3.14, 1);
// 结果: "3.1" (第二个参数指定小数点后保留 1 位)
替换 %+f、%-f、% f 并指定符号:
fmt::format("{:+f}; {:+f}", 3.14, -3.14); // 始终显示符号
// 结果: "+3.140000; -3.140000"fmt::format("{: f}; {: f}", 3.14, -3.14); // 正数前加空格,负数前加负号
// 结果: " 3.140000; -3.140000"fmt::format("{:-f}; {:-f}", 3.14, -3.14); // 仅负数显示符号(与默认行为一致)
// 结果: "3.140000; -3.140000"
替换 % x 和 % o 并转换进制:
fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
// 结果: "int: 42; hex: 2a; oct: 52; bin: 101010" (无前缀)// 带进制前缀(0x、0、0b):
fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42);
// 结果: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010"
带前缀的填充十六进制字节(固定两位):
fmt::format("{:#04x}", 0);
// 结果: "0x00" (前缀 "0x" + 两位十六进制数,总宽度 4)
使用 Unicode 填充字符绘制边框:
fmt::print("┌{0:─^{2}}┐\n" // 顶部边框:用 '─' 填充,总宽度 20"│{1: ^{2}}│\n" // 内容:居中对齐,填充空格,总宽度 20"└{0:─^{2}}┘\n", "", "Hello, world!", 20);
输出:
┌────────────────────┐
│ Hello, world! │
└────────────────────┘
使用特定类型格式化(时间):
#include <fmt/chrono.h>auto t = tm();
t.tm_year = 2010 - 1900; // 年份:2010
t.tm_mon = 7; // 月份:8月(tm_mon 从 0 开始)
t.tm_mday = 4; // 日期:4日
t.tm_hour = 12; // 小时:12
t.tm_min = 15; // 分钟:15
t.tm_sec = 58; // 秒:58fmt::print("{:%Y-%m-%d %H:%M:%S}", t);
// 输出:2010-08-04 12:15:58 (按指定格式输出日期时间)
使用逗号作为千位分隔符(区域设置):
#include <fmt/format.h>// 使用美式英语区域设置(en_US.UTF-8)
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
// s 的值为 "1,234,567,890" (数字按千位分隔)