C++ std::format

在 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() 会清晰很多。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号