1. std::format
直到C++20之前,字符串的格式化一般是通过printf()之类的C风格函数或是C++的I/O流完成的:
1. C风格函数:不推荐,因为它们不是类型安全的,并且无法扩展支持自定义类型;因为字符串和参数是分开的,所以可读性高,且容易翻译成不同语言。例如:
printf("x has value %d and y has value %d.\n", x, y);
2. C++ I/O流:推荐(C++20之前),因为类型安全且可扩展;因为字符串和参数交织在一起,所以可读性差,且难以翻译成不同语言。例如:
std::cout << "x has value" << x << " and y has value " << y << std::endl;
C++20引入了std::format(),用来格式化字符串,它定义在<format>中。它基本结合了C风格函数和C++的I/O流的所有优点,是一种类型安全且可扩展的机制。
format()的第一个参数是待格式化的字符串,后续参数是用于填充待格式化字符串中占位符的值。到目前为止,使用format()时的占位符一般都是一对花括号:{}。在这些花括号内可以是格式为[index][:specifier]的字符串。可以省略所有占位符中的index,也可以为所有占位符指定从零开始的索引,以指明应用于此占位符的第二个和后续参数。如果省略index,则format()的第二个和后续参数传递的值将按给定顺序应用于所有占位符。specifier是一种格式化说明符,用于更改值在输出中格式化的方式,将在下一节详细说明。如果需要输出{or}字符,则需要将其转义为{{or}}。
以下对format()的调用省略了占位符中的显式索引:
auto s1 { std::format("Read {} bytes from {}\n", n, "file1.txt") };
可以按以下方式手动指定索引:
auto s1 { std::format("Read {0} bytes from {1}\n", n, "file1.txt") };
不允许混合使用手动和自动索引。下例使用了一个无效的字符串格式化:
auto s2 { std::format("Read {0} bytes from {}\n", n, "file1.txt") };
可以更改输出字符串中格式化值的顺序,而不需要更改传递到format()的实参的实际顺序。如果要在软件中翻译字符串,这是一个有用的功能。某些语言在句子中不同的顺序。例如,前面的格式化字符串可以翻译成中文,如下所示。在中文中,句子中占位符的顺序是颠倒的,但是由于格式化字符串中的占位符是有索引的,format()函数的参数顺序保持不变(L前缀的用法后面介绍)。
auto s3 { std::format(L"从{1}中读取{0}个字节\n", n, L"file1.txt") }:
2. 格式说明符
格式说明符用于控制值在输出中的格式,前缀为冒号。格式说明符的一般形式如下所示(L与地区特定格式有关,但是在我国好像没啥变化)。
[[fill]align][sign][#][0][width][.precision][L][type]
方括号里的所有说明符都是可选的。
2.1 width
width指定待格式化的值所占字段的最小宽度,例如5。width也可以是另一组花括号,称为动态宽度。如果在花括号中指定了索引,例如{3},则动态宽度的width取自给定索引对应的format()的实参。如果未指定索引,例如{},则width取自format()的实参列表中的下一个参数。示例如下:
int i{ 42 };
std::cout << std::format("|{:5}|\n", i); // | 42|
std::cout << std::format("|{:{}}|\n", i, 8);// | 42|
2.2 [fill]align
[fill]align说明使用哪个字符作为填充字符,然后是值在其字段中的对齐方式。
对齐符号 | 含义 |
---|---|
< | 左对齐(非整数和非浮点数的默认对齐方式) |
> | 右对齐(整数和浮点数的默认对齐方式) |
^ | 居中对齐,在待格式化字符前插入 ⌊ 宽度 − 待格式化字符串长度 2 ⌋ \lfloor\frac{宽度-待格式化字符串长度}{2}\rfloor ⌊2宽度−待格式化字符串长度⌋个字符,在待格式化字符串之后插入 ⌈ 宽度 − 待格式化字符串长度 2 ⌉ \lceil\frac{宽度-待格式化字符串长度}{2}\rceil ⌈2宽度−待格式化字符串长度⌉ |
fill字符会被插入输出中,以确保输出中的字段达到说明符的[width]指定的最小宽度。如果未指定[width],则[fill]align无效。示例如下:
int i { 42 };
std::cout << std::format("|{:7}|\n", i); // | 42|
std::cout << std::format("|{:<7}|\n", i); // |42 |
std::cout << std::format("|{:_>7}|\n", i);// |_____42|
std::cout << std::format("|{:_^7}|\n", i);// |__42___|
2.3 sign
sign符号 | 含义 |
---|---|
- | 只显示负数的符号(默认方式) |
+ | 显示正数和负数的符号 |
空格 | 对负数使用负号,对于正数使用空格 |
示例如下:
int i { 42 };
std::cout << std::format("|{:<5}|\n", i); // |42 |
std::cout << std::format("|{:<+5}|\n", i); // |+42 |
std::cout << std::format("|{:< 5}|\n", i); // | 42 |
std::cout << std::format("|{:< 5}|\n", -i);// |-42 |
2.4
#启用所谓的备用格式规则。如果为整型启用,并且还指定了十六进制、二进制或八进制数字格式,则备用格式会在格式化数字前面插入0x、0X、0b、0B或0。如果为浮点类型启用,则备用格式将始终输出十进制分隔符,即使后面没有数字。
具体示例见下面。
2.5 type
type指定了给定值要被格式化的类型:
类型 | 符号 | 含义 |
整型 |
b B d(整型默认类型) o x X |
二进制
二进制,当指定#时,使用0B而不是0b 十进制 八进制 小写字母abcde的十六进制 大写字母ABCDE的十六进制,当指定#时,使用0X而不是0x |
浮点型 |
e,E f,F g(浮点型默认类型) ,G a,A |
以小写e或大写E表示指数的科学表示法,按照给定精度或6(如果未指定精度)格式化
固定表示法,按照给定精度或6(如果未指定精度)格式化 以小写e或大写E表示指数的通用表示法,按照给定精度或6(如果未指定精度)格式化 带有小写字母(a)或大写字母(A)的十六进制表示法 |
布尔型 |
s(布尔型默认类型)
b B c d o x X |
以文本形式输出true或false
以整型输出1或0 |
字符型 |
c(字符型默认类型)
b B d o x X |
输出字符副本
整数表示 |
字符串 | s(字符串默认类型) | 输出字符串副本 |
指针 | p(指针默认类型) | 0x为前缀的十六进制表示法 |
整型示例如下:
int i { 42 };
std::cout << std::format("|{:10d}|\n", i); // | 42|
std::cout << std::format("|{:10b}|\n", i); // | 101010|
std::cout << std::format("|{:#10b}|\n", i);// | 0b101010|
std::cout << std::format("|{:10o}|\n", i); // | 52|
std::cout << std::format("|{:#10o}|\n", i);// | 052|
std::cout << std::format("|{:10x}|\n", i); // | 2a|
std::cout << std::format("|{:#10X}|\n", i);// | 0X2A|
布尔型示例如下:
bool t { true };
bool f { false };
std::cout << std::format("|{:10s}|\n", t); // | true|
std::cout << std::format("|{:10b}|\n", f); // | 0|
std::cout << std::format("|{:10B}|\n", t); // | 1|
std::cout << std::format("|{:10d}|\n", f); // | 0|
std::cout << std::format("|{:10o}|\n", t); // | 1|
std::cout << std::format("|{:10x}|\n", f); // | 0|
std::cout << std::format("|{:10X}|\n", t); // | 1|
字符型示例如下:
char a { 'c' };
std::cout << std::format("|{:10c}|\n", a); // |c |
std::cout << std::format("|{:#10b}|\n", a); // | 0b1100011|
std::cout << std::format("|{:#10B}|\n", a); // | 0B1100011|
std::cout << std::format("|{:10d}|\n", a); // | 99|
std::cout << std::format("|{:#10o}|\n", a); // | 0143|
std::cout << std::format("|{:#10x}|\n", a); // | 0x63|
std::cout << std::format("|{:#10X}|\n", a); // | 0X63|
字符串的示例如下:
std::string s { "ProCpp" };
std::cout << std::format("|{:_^10}|\n", s); // |__ProCpp__|
2.6 precision
precision只能用于浮点和字符串类型。它的格式为一个点后跟浮点类型要输出的小数点位数(e、f、a),或整个浮点类型包含的有效数字个数(g),或字符串要输出的字符数。就像width一样,也可以是另一组花括号,在这种情况下,它被称为动态精度。precision取自format()的实参列表中的下一个实参或具有给定索引的实参。
浮点型的示例如下:
double d{ 3.1415 / 2.3 };
std::string s{ "Hello, World!" };
std::cout << std::format("|{:12.2g}|\n", d); // | 1.4|
std::cout << std::format("|{:12.3e}|\n", d); // | 1.366e+00|
std::cout << std::format("|{:12.2a}|\n", d); // | 1.5ep+0|
int width{ 12 };
int precision{ 3 };
std::cout << std::format("|{2:{0}.{1}f}|\n", width, precision, d); // | 1.366|
2.7 0
0表示,对于数值,将0插入格式化结果中,以达到[width]指定的最小宽度。这些0插在数值前面,但在符号以及任何0x、0X、0b或0B前缀之后。如果指定了align,则将忽略本选项。
示例如下:
int i { 42 };
double f { 3.1415 / 2.3 };
std::cout << std::format("|{:06d}|\n", i); // |000042|
std::cout << std::format("|{:+06d}|\n", i); // |+00042|
std::cout << std::format("|{:06X}|\n", i); // |00002A|
std::cout << std::format("|{:#06X}|\n", i); // |0X002A|
std::cout << std::format("|{:#06o}|\n", i); // |000052|
std::cout << std::format("|{:012.3f}|\n", f); // |00000001.366|
参考
[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C++20高级编程(第五版)