在 C++20 中,<format> 头文件引入了 std::format() 函数,用来把多个变量或数值按照指定格式拼接成一个新的 std::string 字符串。
以前我们控制小数位数或对齐宽度时,需要连着写 std::fixed、std::setprecision() 等一长串流控制符。如果调用 std::to_string() 把小数转成字符串,也没法直接指定保留几位小数。使用 std::format() 之后,我们只需要通过 {} 占位符把 “文字模板” 和 “待填数据” 分开写,排版和转换都会清爽很多。
占位符 {} 与参数替换
在 C++ 中,使用 std::format() 时,我们先写出一段带有 {} 的格式字符串,再把要填入的数据按顺序写在后面即可。
语法:
#include <format>
std::string 变量名 = std::format("格式字符串", 参数1, 参数2, ...);说明:
格式字符串中的 {} 叫做 “占位符”。需要注意的是,std::format() 的作用是生成并返回一个新的 std::string 字符串,它本身不会直接往屏幕上打印内容。拿到返回的字符串后,我们可以将它保存到变量里,也可以交给 std::cout 输出。
此外,由于 <format> 是 C++20 新增的标准库头文件,我们在本地使用 g++ 编译代码时,记得加上 -std=c++20 参数。
示例 1:按顺序替换占位符
#include <format>
#include <iostream>
#include <string>
int main() {
std::string name = "Jack";
int score = 95;
std::string msg = std::format("{} 得分:{}", name, score);
std::cout << msg << '\n';
return 0;
}运行结果如下。
Jack 得分:95分析:
在这个例子中,格式字符串里包含两个空的 {} 占位符,它们会按照从左到右的顺序,依次被替换为变量 name 和 score 的值。
由于单层花括号 {} 已经被当成了占位符语法,如果我们想在最终字符串里保留普通的花括号字符 { 或 },只需要连写两个相同的花括号即可,比如下面这样:
std::string s = std::format("集合:{{1, 2, {}}}", 3);
std::cout << s << '\n';这段代码运行后会输出 "集合:{1, 2, 3}"。其中外层的 {{ 和 }} 会转义保留为一对普通花括号,只有最里面那个单层的 {} 会被替换成数字 3。
除了按默认顺序替换之外,我们还可以在花括号里写上从 0 开始的参数编号,比如 {0} 表示第 1 个数据参数,{1} 表示第 2 个数据参数。当同一个变量需要在句子里重复出现多次时,这种写法非常省事。
示例 2:按编号指定参数位置
#include <format>
#include <iostream>
#include <string>
int main() {
std::string lang = "C++";
int year = 2026;
std::string s = std::format("{0}:{1} 年学 {0}", lang, year);
std::cout << s << '\n';
return 0;
}运行结果如下。
C++:2026 年学 C++分析:
{0} 对应后面的第 1 个数据参数 lang,{1} 对应第 2 个数据参数 year。虽然结果里出现了两次 "C++",但我们只需要在参数列表中传入一次 lang 就可以了。
这里小伙伴们要注意,在同一个格式字符串里,“不带编号的 {}” 和 “带编号的 {0}” 不能混在一起使用。
// 正确方式
std::string s1 = std::format("{} 和 {}", a, b);
std::string s2 = std::format("{0} 和 {1}", a, b);
// 错误方式
std::string s3 = std::format("{} 和 {1}", a, b);在上面代码中,s1 全部按顺序自动替换,s2 全部按编号替换,这两种写法都是合法的。而 s3 把自动顺序和手动编号混在了一起,编译时就会直接报错。同样地,如果占位符的个数多于后面传入的参数个数,也无法通过编译。
注意 std::format() 会在编译阶段直接检查格式字符串与参数类型是否匹配,因此它的第 1 个参数必须是双引号字符串字面量,或者是使用 constexpr 定义的编译期常量。如果我们把普通的运行期 std::string 变量作为第 1 个格式参数传进去,将无法通过编译。
控制小数精度与整数进制
如果只写空的 {},数据会按照默认格式转换成文本。想要进一步控制小数位数、整数进制或对齐方式,我们可以在 {} 内部写一个英文冒号(:),并在冒号后面填上格式说明符。如果同时指定了参数编号,就把编号写在冒号左边,例如 {0:.2f}。
在实际开发中,常用的格式说明符如下表所示。
| 格式写法 | 功能说明 | 示例结果 |
|---|---|---|
| {:.nf} | 保留小数点后 n 位(四舍五入) | "{:.2f}" 格式化 3.146 得 "3.15" |
| {:d} / {:x} / {:b} | 按十进制 / 十六进制 / 二进制输出 | "{:x}" 格式化 255 得 "ff" |
| {:#x} / {:#b} | 带 0x 或 0b 前缀输出整数 | "{:#b}" 格式化 10 得 "0b1010" |
| {:<w} / {:>w} / {:^w} | 设最小宽度 w 并左 / 右 / 居中对齐 | "{:>5}" 格式化 42 得 " 42" |
| {:0w} / {:*<w} | 使用 0 或指定字符填充空白 | "{:04d}" 格式化 7 得 "0007" |
下面我们先通过一个例子看看如何控制小数位数与整数进制,至于表格后两行的宽度与对齐填充,我们在后面紧接着演示。
示例 3:控制小数精度与进制
#include <format>
#include <iostream>
int main() {
double price = 12.3456;
int num = 42;
std::cout << std::format("2 位小数:{:.2f}", price) << '\n';
std::cout << std::format("1 位小数:{:.1f}", price) << '\n';
std::cout << std::format("十六进制:{:#x}", num) << '\n';
std::cout << std::format("二进制:{:#b}", num) << '\n';
return 0;
}运行结果如下。
2 位小数:12.35
1 位小数:12.3
十六进制:0x2a
二进制:0b101010分析:
{:.2f} 中的冒号 : 表示后面开始写格式要求,.2 表示保留小数点后 2 位,f 表示定点小数(fixed)格式。因此 12.3456 四舍五入后生成了 "12.35"。
而在 {:#x} 和 {:#b} 中,x 和 b 分别代表十六进制和二进制,中间的 # 号用来自动补上 0x 和 0b 前缀。对于这个例子来说,如果我们把 # 去掉写成 {:x} 和 {:b},输出结果就会变成不带前缀的 2a 和 101010。
设置字段宽度、对齐与填充
在打印对齐内容、生成固定位数的编号或同时控制宽度与小数位数时,我们可以在冒号后面指定最小宽度、对齐方向以及填充字符。
对于 <、>、^ 这 3 个对齐符号,小伙伴们只需要记住一个直观的规律:尖括号的尖头朝向哪边,文字就往哪边靠。其中,< 尖头朝左表示左对齐,> 尖头朝右表示右对齐。而 ^ 像小房顶一样处在中间,表示居中对齐。下面我们来看一个例子。
示例 4:设置宽度与对齐填充
#include <format>
#include <iostream>
#include <string>
int main() {
std::string text = "C++";
int id = 7;
double price = 12.5;
std::cout << std::format("|{:<8}|", text) << '\n';
std::cout << std::format("|{:^8}|", text) << '\n';
std::cout << std::format("|{:>8}|", text) << '\n';
std::cout << std::format("{:*^11}", text) << '\n';
std::cout << std::format("工号:{:04d}", id) << '\n';
std::cout << std::format("单价:{:>8.2f}", price) << '\n';
return 0;
}运行结果如下。
|C++ |
| C++ |
| C++|
****C++****
工号:0007
单价: 12.50分析:
从前 3 行输出可以看出,{:<8}、{:^8} 和 {:>8} 都将字段的最小宽度设为了 8 个字符。由于 "C++" 本身占 3 个字符,不足的 5 个位置默认使用空格补齐。
如果我们想把空白处的空格换成其他符号,只需要把填充字符写在冒号后面、对齐符号前面即可。比如 {:*^11} 表示总宽度为 11 个字符,使用星号 * 在左右两侧填充并居中显示。{:04d} 则表示整数至少占 4 位,不足 4 位时在左侧补 0。
当我们需要同时控制 “对齐宽度” 和 “小数位数” 时,按顺序先写宽度、再写 .nf 即可。比如 {:>8.2f} 表示总宽度占 8 个字符且右对齐,同时保留 2 位小数。注意小数点本身也占 1 个字符,"12.50" 一共是 5 个字符,因此左侧自动补了 3 个空格。
另外大家还要留意,这里指定的宽度只是 “最小宽度”。如果实际内容的长度已经超过了设定的宽度,std::format() 会完整保留全部字符,不会把多出来的内容截断。在实际开发中,如果只是简单打印一两个变量,我们直接使用 std::cout << 最省事。当需要拼接多个变量或精细控制小数与对齐格式时,优先使用 std::format() 会清晰很多。
