当前位置: 首页 > news >正文

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::formatfmt::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 按顺序为 012……,则可以全部省略(而不仅仅是部分省略),并且数字 012…… 将按该顺序自动插入。

命名参数可以通过其名称或索引引用。

以下是一些简单的格式字符串示例:

"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' 不能用于布尔值。如果未指定呈现类型,布尔值将使用文本表示形式(truefalse)进行格式化。

可用的浮点数呈现类型:

类型含义
'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' 时,范围元素类型必须是字符类型(如 charwchar_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" (数字按千位分隔)
http://www.xdnf.cn/news/611857.html

相关文章:

  • CARIS HIPS and SIPS 12.1是专业的多波束水深数据和声呐图像处理软件
  • Graph RAG应用实战
  • socc 19 echash论文部分解读
  • 深度学习优化器相关问题
  • yolov5 安卓运行
  • Docker部署Zookeeper集群
  • C++学习之打车软件—JNI终端编程业务④https协议session开发
  • Open CASCADE学习|非线性方程组求解技术详解
  • 公司内网本地的SVN没有公网IP地址,在家外网也能远程访问SVN服务!
  • postgresql 的优劣势比较
  • 多模态理解大模型高性能优化丨前沿多模态模型开发与应用实战第七期
  • WPF性能优化之延迟加载(解决页面卡顿问题)
  • Python面向对象编程:封装、继承与多态
  • 七彩喜适老化改造:让每个空间成为长者尊严的守护者
  • Jouier 普及组十连测 R4
  • leetcode-快慢指针系列
  • 利用chat搜索需求相关视频链接
  • 45道工程模块化高频题整理(附答案背诵版)
  • `ol/proj`简介
  • 在日本,书法也是美术
  • WebSphere Application Server(WAS)8.5.5教程第十二讲:EJB
  • Zephyr OS 使能和失能蓝牙协议栈的操作
  • [linux] git强行拉取并覆盖
  • VR全景制作方法都有哪些?需要注意什么?
  • IT | 词汇科普手册Ⅱ
  • Leetcode 3313. 查找树中最后标记的节点
  • FreeGPT+内网穿透外网远程连接使用,搞定ChatGPT访问难题!
  • LPRNet实现车牌识别并完成ONNX和TensorRT推理
  • 怎么判断一个Android APP使用了Electron 这个跨端框架
  • 【动态规划】5 从一次函数出发推导斜率优化dp