1. 引言
在本文中,我们将详细讨论C++格式化的传统问题与挑战,以及C++20标准中引入std::format
的背景。
传统C++格式化的问题与挑战
传统C++格式化存在一些问题与挑战,主要包括以下几点:
- 可读性差:使用C++中的
printf
和scanf
家族函数进行格式化输出和输入时,它们的语法较为复杂,难以阅读。在较大的代码项目中,可读性差会导致维护困难。 - 类型安全性差:
printf
和scanf
等函数无法在编译期间检查参数的类型是否正确。这可能导致运行时错误,甚至引发程序崩溃。 - 不够灵活:对于复杂的格式化需求,
printf
和scanf
等函数提供的功能有限。例如,它们不支持自定义类型的格式化,也不方便处理宽字符和多字节字符集。 - 性能开销:由于传统的格式化方法在运行时需要处理格式字符串,它们可能导致额外的性能开销。
C++20引入std::format的背景
鉴于传统C++格式化方法的局限性,C++20标准中引入了std::format
库,旨在提供一种更现代、更安全、更灵活的格式化方法。引入std::format
的主要动机包括:
- 提高可读性:
std::format
采用了一种更加简洁、易懂的语法,使得格式化字符串更具可读性。 - 增强类型安全:
std::format
在编译期间就可以检查参数类型的正确性,从而降低运行时错误的风险。 - 扩展功能:
std::format
支持自定义类型的格式化,同时兼容宽字符和多字节字符集。这使得开发人员能够满足更为复杂的格式化需求。 - 性能优化:
std::format
设计时充分考虑了性能问题,相比传统的格式化方法,它在许多场景下能够提供更高的性能。
总之,std::format
作为C++20标准的一部分,旨在解决传统C++格式化方法的问题,并为开发者提供一种更现代、更安全、更灵活的格式化工具。
2. std::format简介
在本节中,我们将简要介绍std::format
的基本概念,并对比std::format
与printf
和iostreams
之间的差异。
std::format的基本概念
std::format
是C++20标准库中新增的一个格式化工具,它基于Python中的str.format()
函数,提供了一种类型安全且易于阅读的字符串格式化方法。std::format
的主要特点包括:
- 替换字段:
std::format
使用花括号{}
作为替换字段的占位符。这些替换字段在格式化时会被相应的参数值替换。 - 格式规范:
std::format
支持在替换字段内部定义格式规范,例如指定输出宽度、对齐方式和填充字符等。格式规范使用冒号:
分隔,放在花括号内。 - 编译时类型检查:
std::format
在编译期间检查参数类型的正确性,以提高类型安全性。 - 自定义类型支持:
std::format
可以通过重载formatter
特化来支持自定义类型的格式化。
std::format与printf、iostreams的对比
下面我们将对比std::format
与printf
和iostreams
之间的主要差异:
- 可读性:
std::format
使用花括号作为占位符,并允许在占位符内定义格式规范。这使得格式化字符串更具可读性,相较于printf
和iostreams
更为简洁明了。
示例:
std::cout << std::format("Hello, {}!\n", "World"); // std::format printf("Hello, %s!\n", "World"); // printf std::cout << "Hello, " << "World" << "!\n"; // iostreams
- 类型安全:
std::format
在编译期间检查参数类型的正确性,而printf
在运行时检查类型。iostreams
也具有类型安全性,但std::format
更接近printf
的语法,使得从printf
迁移到std::format
更容易。 - 扩展性:
std::format
支持自定义类型的格式化,而printf
仅支持内置类型。iostreams
通过重载插入和提取操作符支持自定义类型,但std::format
提供更为统一的扩展方法。 - 性能:
std::format
在设计时充分考虑了性能问题,因此在许多场景下性能优于iostreams
。而与printf
相比,std::format
的性能表现也非常出色。
综上所述,std::format
在可读性、类型安全性、扩展性和性能方面都表现优异,成为现代C++编程中推荐的字符串格式化工具。
高效使用std::format的理由
以下是为什么应该高效使用std::format
的几个理由:
- 统一的格式化语法:
std::format
提供了一种统一的格式化语法,无论是内置类型还是自定义类型,都可以使用相同的方法进行格式化。这有助于简化代码并降低维护成本。 - 简化代码:由于
std::format
提供了更简洁的语法,使用它可以减少代码量,使代码更易于理解。相较于printf
和iostreams
,std::format
更适合处理复杂的字符串格式化需求。 - 避免运行时错误:
std::format
在编译期间检查参数类型,能够减少因类型错误导致的运行时错误。这有助于提高代码的健壮性和稳定性。 - 易于迁移:对于已经习惯使用
printf
的开发者,std::format
提供了类似的语法和功能,可以轻松从printf
迁移到std::format
。 - 便于调试和优化:
std::format
的性能表现优异,且支持各种格式化选项,方便开发者进行调试和性能优化。
总之,std::format
作为C++20标准库的一部分,为开发者提供了强大、易用的字符串格式化工具。使用std::format
可以简化代码、提高可读性、增强类型安全性,并有助于提高代码的健壮性和性能。因此,在现代C++编程中,高效使用std::format
是非常重要的。
3. 基本用法
在本节中,我们将介绍std::format
的基本用法,包括格式字符串与占位符、类型规格与格式选项的使用。
格式字符串与占位符
std::format
使用格式字符串来定义输出的格式。格式字符串中的占位符用花括号{}
表示,可以包含以下几个部分:
- 参数索引:位于花括号内的数字,用于指定要替换的参数的位置。例如,
{0}
表示第一个参数,{1}
表示第二个参数,依此类推。 - 格式规范:位于冒号
:
之后的部分,用于指定参数的格式选项。例如,{:d}
表示将参数格式化为十进制整数。 - 文本:花括号之间可以包含任意文本,这些文本将原样输出。例如,
{0} is {1}
中的is
会原样输出。
以下是一些基本的例子:
#include <iostream> #include <format> int main() { int age = 30; double pi = 3.1415926; std::string name = "Alice"; std::cout << std::format("My name is {0} and I am {1} years old.\n", name, age); std::cout << std::format("Pi is approximately {0}.\n", pi); return 0; }
类型规格与格式选项
std::format
支持各种类型规格与格式选项,以便对输出进行详细的控制。以下是一些常见的类型规格与格式选项:
- 整数:
d
:十进制整数。x
:小写十六进制整数。X
:大写十六进制整数。o
:八进制整数。b
:二进制整数。
示例:
std::cout << std::format("{0:d} {0:x} {0:X} {0:o} {0:b}\n", 42);
- 浮点数:
f
:固定点表示法。e
:小写科学计数法。E
:大写科学计数法。g
:根据值选择最简表示法(f
或e
)。G
:根据值选择最简表示法(f
或E
)。
示例:
std::cout << std::format("{0:f} {0:e} {0:E} {0:g} {0:G}\n", 3.1415926535);
- 字符串:
s
:字符串。
示例:
std::cout << std::format("{:s}\n", "Hello, World!");
- 宽度、对齐和填充:
<
:左对齐。>
:右对齐。^
:居中对齐。数字
:指定输出宽度。字符
:指定填充字符。
示例:
std::cout << std::format("{:<10} | {:>10} | {:^10}\n", "left", "right", "center"); std::cout << std::format("{:*<10} | {:#>10} | {:_^10}\n", "left", "right", "center");
- 精度
对于浮点数,精度用于指定小数点后的位数;对于字符串,精度用于指定最大输出长度。
示例:
std::cout << std::format("{:.2f} | {:.3e} | {:.4s}\n", 3.1415926, 12345.6789, "abcdefgh");
- 整数和浮点数的进位:
整数和浮点数的进位可以使用#
选项,它会在八进制和十六进制数字前添加0
或0x
(0X
)前缀,或在浮点数上强制输出小数点。
示例:
std::cout << std::format("{:#x} | {:#o} | {:#f}\n", 42, 42, 3.14);
- 正负号:
使用+
选项可以强制输出正数的正号。
示例:
std::cout << std::format("{:+d} | {:+f}\n", 42, 3.14);
- 自定义类型
: 要格式化自定义类型,需要为类型特化std::formatter
模板,并提供parse
和format
成员函数。这使得std::format
可以以一种统一的方式处理内置类型和自定义类型。
示例:
struct Point { int x, y; }; template<> struct std::formatter<Point> { auto parse(format_parse_context& ctx) { return ctx.begin(); } auto format(const Point& p, format_context& ctx) { return std::format_to(ctx.out(), "({:d}, {:d})", p.x, p.y); } }; std::cout << std::format("{0}\n", Point{3, 4});
4. 格式化数字
在使用std::format
时,您可能会需要更多地控制数字的格式。在本节中,我们将详细讨论数字格式化的选项,包括宽度、精度、填充、正负号显示、进制转换以及浮点数格式化选项。
控制数字的宽度、精度与填充
要控制数字的宽度,请在格式说明符中指定一个整数。此外,您还可以使用0
指定填充字符,例如{:05}
表示将数字格式化为至少5个字符宽,不足部分用零填充。以下是一些示例:
std::cout << std::format("{:5}", 42); // " 42" std::cout << std::format("{:05}", 42); // "00042"
对于浮点数,您可以使用.
后接一个整数来指定精度。例如:
std::cout << std::format("{:.2f}", 3.14159); // "3.14"
显示或隐藏正负号
要显示数字的正负号,可以使用+
标志。例如:
std::cout << std::format("{:+}", 42); // "+42" std::cout << std::format("{:+}", -42); // "-42"
进制转换(十进制、十六进制、八进制等)
要将数字格式化为其他进制,可以使用以下格式说明符:
d
:十进制(默认)x
:十六进制(小写字母)X
:十六进制(大写字母)o
:八进制b
:二进制(小写字母)B
:二进制(大写字母)
以下是一些示例:
std::cout << std::format("{:x}", 42); // "2a" std::cout << std::format("{:X}", 42); // "2A" std::cout << std::format("{:o}", 42); // "52" std::cout << std::format("{:b}", 42); // "101010"
浮点数格式化选项
对于浮点数,您可以使用以下格式说明符:
f
:定点表示(默认)F
:定点表示(无穷大和非数字为大写表示)e
:科学计数法(小写字母)E
:科学计数法(大写字母)g
:通用格式,根据值的大小和指定精度自动选择定点表示或科学计数法(小写字母)G
:通用格式,根据值的大小和指定精度自动选择定点表示或科学计数法(大写字母)
以下是一些示例:
std::cout << std::format("{:.2F}", 42.123); // "42.12" std::cout << std::format("{:.2e}", 42.123); // "4.21e+01" std::cout << std::format("{:.2E}", 42.123); // "4.21E+01" std::cout << std::format("{:.2g}", 42.123); // "42.12" std::cout << std::format("{:.2G}", 42.123); // "42.12" std::cout << std::format("{:.2g}", 0.000421); // "0.000421" std::cout << std::format("{:.2G}", 0.000421); // "0.000421"
通过上述示例,您可以看到不同浮点数格式化选项的使用方法。这使得std::format成为一个非常灵活和强大的工具,能够处理各种数字格式化需求。请尝试根据您的应用需求进行调整和组合这些选项。
5. 格式化文本
在使用std::format
时,除了处理数字之外,您还需要考虑如何格式化文本。本节将讨论如何使用std::format
来处理字符串的宽度、填充、特殊字符、转义以及多语言和Unicode字符。
控制字符串的宽度与填充
要设置字符串的最小宽度,请在格式说明符中指定一个整数。您还可以通过在整数前加上填充字符来设置填充字符。以下是一些示例:
std::cout << std::format("{:10}", "hello"); // "hello " std::cout << std::format("{:_<10}", "hello"); // "hello_____"
处理特殊字符与转义
要在格式化字符串中包含大括号{}
,请使用两个连续的大括号{{
或}}
进行转义。以下是一个示例:
std::cout << std::format("The set contains {{1, 2, 3}}"); // "The set contains {1, 2, 3}"
要在格式化字符串中包含反斜杠和其他特殊字符,请使用反斜杠进行转义,如\n
表示换行符,\t
表示制表符等。例如:
std::cout << std::format("Line 1\\nLine 2"); // "Line 1\nLine 2"
使用std::format处理多语言与Unicode
std::format
支持Unicode字符和多语言文本处理。为了确保正确处理Unicode字符,请使用u8
前缀表示UTF-8编码的字符串字面值。以下是一个示例:
std::cout << std::format(u8"你好,世界!"); // "你好,世界!"
在处理Unicode字符串时,确保使用正确的编码,否则可能会导致乱码或无法解释的字符。std::format
兼容C++17及更高版本的std::u8string
类型,允许您更轻松地处理多语言文本。
总之,std::format
提供了处理字符串宽度、填充、特殊字符、转义以及多语言和Unicode字符的能力。这使得std::format
成为一个非常适用于现代C++应用程序的强大工具。
6. 格式化日期与时间
std::format
可以与C++的chrono
库一起使用,方便地格式化日期和时间。本节将讨论如何使用std::format
处理时间点、持续时间、时间格式化选项以及本地化日期和时间的显示。
使用chrono库处理时间点与持续时间
chrono
库提供了表示时间点和持续时间的类,如system_clock::time_point
、steady_clock::time_point
、duration
等。要使用std::format
格式化这些类型,首先需要包含和
头文件。
以下是一个示例:
#include <chrono> #include <format> #include <iostream> int main() { auto now = std::chrono::system_clock::now(); auto seconds_since_epoch = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()); std::cout << std::format("Seconds since epoch: {}\n", seconds_since_epoch.count()); }
时间格式化选项
要格式化日期和时间,可以使用扩展的格式说明符,如下所示:
%Y
:四位年份%m
:月份(01-12)%d
:月份中的第几天(01-31)%H
:小时(00-23)%M
:分钟(00-59)%S
:秒(00-60,因闰秒可能为60)
为了使用这些格式化选项,需要先将chrono
中的time_point
转换为std::tm
结构,并包含头文件。以下是一个示例:
#include <chrono> #include <format> #include <iomanip> #include <iostream> int main() { auto now = std::chrono::system_clock::now(); auto now_t = std::chrono::system_clock::to_time_t(now); auto now_tm = *std::localtime(&now_t); std::cout << std::format("{:%Y-%m-%d %H:%M:%S}\n", now_tm); }
本地化日期与时间的显示
要显示本地化的日期和时间,可以使用std::locale。使用imbue()函数将流与特定的语言环境关联起来。以下是一个示例:
#include <chrono> #include <format> #include <iomanip> #include <iostream> #include <locale> int main() { auto now = std::chrono::system_clock::now(); auto now_t = std::chrono::system_clock::to_time_t(now); auto now_tm = *std::localtime(&now_t); std::locale::global(std::locale("")); std::cout.imbue(std::locale()); std::cout << std::format("{:%c}\n", now_tm); }
请注意,std::locale::global()
和imbue()
函数的参数取决于您的平台和语言设置。本示例设置为系统默认语言环境。您还可以为特定的流或字符串指定语言环境。
通过以上方法,您可以使用std::format
来灵活地处理和格式化日期与时间。与C++的chrono
库结合使用,可以更方便地处理时间点和持续时间,同时允许您定制时间格式化选项以适应不同的应用场景。同时,通过std::locale
类,您还可以实现日期和时间的本地化显示,以适应不同地区的用户。
下面是一些日期和时间格式化选项的示例:
#include <chrono> #include <format> #include <iomanip> #include <iostream> #include <locale> int main() { auto now = std::chrono::system_clock::now(); auto now_t = std::chrono::system_clock::to_time_t(now); auto now_tm = *std::localtime(&now_t); std::cout << std::format("{:%A, %B %d, %Y}\n", now_tm); // 显示星期、月份、日期和年份,例如:"Sunday, April 09, 2023" std::cout << std::format("{:%D}\n", now_tm); // 以MM/DD/YY格式显示日期,例如:"04/09/23" std::cout << std::format("{:%T}\n", now_tm); // 以HH:MM:SS格式显示时间,例如:"17:30:59" std::cout << std::format("{:%r}\n", now_tm); // 以12小时制显示时间,例如:"05:30:59 PM" }
7. 自定义类型的格式化
std::format
允许您为自定义类型实现格式化支持,这为您的自定义类型提供了更好的输出显示。要实现自定义类型的格式化支持,您需要特化std::formatter
。在本节中,我们将讨论如何为自定义类型实现格式化输出。
实现自定义类型的格式化支持
要为自定义类型实现格式化支持,您需要为其特化std::formatter
,并重载parse()
和format()
成员函数。
以下是实现自定义类型格式化输出的步骤:
- 包含
头文件。
- 为您的自定义类型特化
std::formatter
。 - 在特化的
std::formatter
中,重载parse()
和format()
成员函数。
使用fmt::formatter特化
以下是一个简单的自定义类型(Person
)和std::formatter
特化的示例:
#include <format> #include <iostream> #include <string> struct Person { std::string name; int age; }; template <> struct std::formatter<Person> { constexpr auto parse(format_parse_context& ctx) { auto it = ctx.begin(); auto end = ctx.end(); if (it != end && *it != '}') throw format_error("Invalid format"); return it; } auto format(const Person& p, format_context& ctx) { return format_to(ctx.out(), "{} ({})", p.name, p.age); } };
示例:为自定义类型实现格式化输出
现在我们已经为Person
类型实现了std::formatter
特化,可以使用std::format
函数轻松格式化Person
对象了:
int main() { Person alice{"Alice", 30}; std::cout << std::format("{}", alice) << std::endl; // 输出:"Alice (30)" }
通过实现std::formatter
特化并重载parse()
和format()
成员函数,您可以为自定义类型提供灵活且易于使用的格式化支持。这可以大大提高您的C++代码的可读性和维护性。
8. std::format的高级技巧与应用
在本节中,我们将讨论std::format
的一些高级技巧和应用,包括动态生成格式字符串、与其他标准库组件(如容器、文件操作等)的结合使用以及提高格式化性能的建议。
格式字符串的动态生成
在某些情况下,您可能需要根据运行时参数动态生成格式字符串。可以使用std::string
或其他字符串处理方法来实现这一点。例如,您可能需要根据用户输入设置小数点后的位数:
#include <format> #include <iostream> int main() { double pi = 3.141592653589793; int precision = 2; std::string format_str = "{:." + std::to_string(precision) + "f}"; std::cout << std::format(format_str, pi) << std::endl; // 输出:"3.14" }
使用std::format与其他标准库组件(如容器、文件操作等)
std::format
可以与其他标准库组件(如容器、文件操作等)一起使用,以提供更高级的格式化功能。以下是一些示例:
- 与容器一起使用:
#include <format> #include <iostream> #include <vector> int main() { std::vector<int> numbers = {1, 2, 3, 4, 5}; std::string result = std::format("Numbers: ["); for (const auto& num : numbers) { result += std::format("{}, ", num); } result = result.substr(0, result.size() - 2) + "]"; std::cout << result << std::endl; // 输出:"Numbers: [1, 2, 3, 4, 5]" }
- 文件操作一起使用:
#include <format> #include <fstream> #include <iostream> int main() { std::ofstream output_file("output.txt"); output_file << std::format("{:<10} {:>10}\n", "Name", "Score"); output_file << std::format("{:<10} {:>10}\n", "Alice", 95); output_file << std::format("{:<10} {:>10}\n", "Bob", 80); output_file.close(); std::cout << "Output saved to output.txt" << std::endl; }
提高格式化性能的建议
虽然std::format
在很多方面都比传统的格式化方法更高效,但在某些情况下,性能仍然是一个值得关注的问题。以下是一些建议,可以帮助您提高格式化性能:
- 避免频繁构建和销毁格式化字符串:在循环或高频调用的函数中避免重复构建格式化字符串。考虑将格式化字符串预先计算并存储为常量或静态变量。
- 减少不必要的字符串连接:在可能的情况下,尽量避免使用
+
运算符连接字符串。可以使用
std::format直接构建最终字符串,而不是分段拼接。例如,可以将多个
std::format`调用替换为一个带有多个占位符的调用。 - 使用预分配的内存:为频繁使用的字符串分配足够的预先分配的内存,以减少内存分配和重新分配的开销。例如,您可以使用
std::string::reserve()
函数为字符串预留足够的空间。 - 避免不必要的类型转换:在可能的情况下,尽量避免在格式化之前将数据类型转换为其他类型。例如,不要在格式化之前将
int
转换为std::string
,而是直接使用int
类型的格式规范。 - 选择合适的容器和算法:根据具体应用场景选择合适的容器和算法,以实现最佳性能。例如,对于需要快速插入和删除元素的场景,使用
std::list
或std::deque
而不是std::vector
。
通过遵循以上建议,您可以确保在使用std::format
进行格式化操作时实现最佳性能。这将有助于提高您的C++应用程序的整体性能和响应速度。
9. 结论与展望
std::format在现代C++中的地位与作用
std::format
是C++20中引入的一个重要特性,它在现代C++中扮演着重要的角色。与传统的C++格式化方法相比,如printf
和iostreams
,std::format
提供了更为强大、灵活和安全的格式化功能。它支持类型安全,易于扩展,支持自定义类型和多语言环境。std::format
有助于提高代码的可读性和维护性,使得C++在格式化方面与其他现代编程语言保持同步。
与其他语言的格式化库的比较
std::format
的设计受到了其他编程语言中格式化库的启发,如Python的str.format()
和f-string
,以及Rust的std::fmt
。与这些库相比,std::format
具有类似的功能和语法,同时充分利用了C++的类型系统和编译时特性,以实现最佳性能。
C++标准化进程中格式化相关的未来发展
C++标准化进程将继续发展和完善格式化功能。例如,C++23中可能会引入std::format
的扩展,以提供更丰富的格式选项和本地化支持。此外,C++社区也将继续关注其他语言的发展,以确保C++在格式化方面与时俱进。
总之,std::format
为C++开发者提供了一种强大且易于使用的格式化工具。它不仅带来了更好的类型安全和扩展性,还为未来的C++标准提供了一个坚实的基础。作为现代C++的一部分,std::format
将继续发展和完善,为C++程序员提供更高效和灵活的格式化解决方案。
10.一百个std::format使用示例
#include <iostream> #include <format> int main() { int num = 42; double pi = 3.1415926535; std::string str = "Hello, World!"; std::string long_str = "This is a very long string to showcase precision"; std::cout << std::format("1. {:d}\n", num); // 1. 整数十进制 std::cout << std::format("2. {:x}\n", num); // 2. 整数小写十六进制 std::cout << std::format("3. {:X}\n", num); // 3. 整数大写十六进制 std::cout << std::format("4. {:o}\n", num); // 4. 整数八进制 std::cout << std::format("5. {:b}\n", num); // 5. 整数二进制 std::cout << std::format("6. {:f}\n", pi); // 6. 浮点数固定点表示 std::cout << std::format("7. {:e}\n", pi); // 7. 浮点数小写科学计数法 std::cout << std::format("8. {:E}\n", pi); // 8. 浮点数大写科学计数法 std::cout << std::format("9. {:g}\n", pi); // 9. 浮点数简化小写表示法 std::cout << std::format("10. {:G}\n", pi); // 10. 浮点数简化大写表示法 std::cout << std::format("11. {:s}\n", str); // 11. 字符串 std::cout << std::format("12. {:<10}\n", str); // 12. 左对齐 std::cout << std::format("13. {:>10}\n", str); // 13. 右对齐 std::cout << std::format("14. {:^10}\n", str); // 14. 居中对齐 std::cout << std::format("15. {:*>10}\n", str); // 15. 指定填充字符 std::cout << std::format("16. {:.5s}\n", long_str); // 16. 字符串精度 std::cout << std::format("17. {:.2f}\n", pi); // 17. 浮点数精度 std::cout << std::format("18. {:#x}\n", num); // 18. 整数进制前缀 std::cout << std::format("19. {:#o}\n", num); // 19. 整数八进制前缀 std::cout << std::format("20. {:#f}\n", pi); // 20. 浮点数强制输出小数点 std::cout << std::format("21. {:d}\n", -num); // 21. 负数整数 std::cout << std::format("22. {:f}\n", -pi); // 22. 负数浮点数 std::cout << std::format("23. {:g}\n", 1e-10); // 23. 浮点数简化表示法对于极小值的处理 std::cout << std::format("24. {:G}\n", 1e+10); // 24. 浮点数简化大写表示法对于极大值的处理 std::cout << std::format("25. {:%}\n", 0.5); // 25. 百分数表示 std::cout << std::format("26. {:%}\n", 0.99); // 26. 百分数表示 std::cout << std::format("27. {:%}\n", -0.5); // 27. 负数百分数表示 std::cout << std::format("28. {:L}\n", true); // 28. 布尔值为true时显示字母L std::cout << std::format("29. {:P}\n", false); // 29. 布尔值为false时显示字母P std::cout << std::format("30. {0:*<10} {1:#>10} {2:_^10}\n", "left", "right", "center"); // 30. 多个参数填充、对齐 std::cout << std::format("31. {:+d}\n", num); // 31. 强制输出正数的正号 std::cout << std::format("32. {:+f}\n", pi); // 32. 强制输出正数的正号 std::cout << std::format("33. {:-d}\n", num); // 33. 显示正数时省略正号 std::cout << std::format("34. {: d}\n", num); // 34. 为正数留出空格 std::cout << std::format("35. {:10.2f}\n", pi); // 35. 指定宽度和精度 std::cout << std::format("36. {0:d} - {1:s}\n", num, str); // 36. 指定参数位置 std::cout << std::format("37. {:10.2e}\n", 12345.6789); // 37. 科学计数法指定宽度和精度 std::cout << std::format("38. {:05d}\n", num); // 38. 整数补零 std::cout << std::format("39. {:010.2f}\n", pi); // 39. 浮点数补零 std::cout << std::format("40. {:+010.2f}\n", pi); // 40. 浮点数补零并强制输出正号 std::cout << std::format("41. {0:*<10} {1:#>10}\n", str, num); // 41. 分别指定左填充和右填充 std::cout << std::format("42. {0:.>{1}}\n", str, 15); // 42. 动态指定宽度 std::cout << std::format("43. {0:^{1}}\n", str, 20); // 43. 动态指定居中对齐的宽度 std::cout << std::format("44. {0:*>{1}.>{2}}\n", long_str, 20, 10); // 44. 动态指定宽度、精度和填充字符 std::cout << std::format("45. {:n}\n", 1000000); // 45. 使用本地化的千位分隔符 std::cout << std::format("46. {:04X}\n", num); // 46. 大写十六进制补零 std::cout << std::format("47. {:c}\n", 65); // 47. 将整数解释为ASCII字符 std::cout << std::format("48. {:010,d}\n", 1000000); // 48. 整数千位分隔符和补零 std::cout << std::format("49. {:20,}\n", 1234567890); // 49. 整数千位分隔符和宽度 std::cout << std::format("50. {0:%}{1:->10}\n", 0.5, "right"); // 50. 百分数表示与右对齐结合 std::cout << std::format("51. {0:x}{1:10.2f}\n", num, pi); // 51. 同一行输出十六进制整数和浮点数 std::cout << std::format("52. {0:<10}{1:>10}\n", str, num); // 52. 左对齐字符串和右对齐数字 std::cout << std::format("53. {0:*^20s}\n", str); // 53. 居中字符串并指定填充字符 std::cout << std::format("54. {:{},.{}f}\n", 1234567.89, 15, 2); // 54. 动态指定宽度、精度和千位分隔符 std::cout << std::format("55. {:A}\n", 0x1.921fb54442d18p+1); // 55. 十六进制浮点数大写表示 std::cout << std::format("56. {:a}\n", 0x1.921fb54442d18p+1); // 56. 十六进制浮点数小写表示 std::cout << std::format("57. {:%.2%}\n", 0.123456); // 57. 百分数表示并指定精度 std::cout << std::format("58. {:0>10}\n", num); // 58. 数字补零并右对齐 std::cout << std::format("59. {:0>10}\n", str); // 59. 字符串补零并右对齐 std::cout << std::format("60. {0:#010x} {1:.{}f}\n", num, pi, 3); // 60. 整数进制前缀、浮点数动态精度 std::cout << std::format("61. {0:0{1}X}\n", num, 6); // 61. 动态指定整数十六进制大写表示的宽度 std::cout << std::format("62. {0:0{1}x}\n", num, 8); // 62. 动态指定整数十六进制小写表示的宽度 std::cout << std::format("63. {0:0{1}d}\n", num, 10); // 63. 动态指定整数十进制表示的宽度 std::cout << std::format("64. {0:0{1}o}\n", num, 8); // 64. 动态指定整数八进制表示的宽度 std::cout << std::format("66. {:0{width}.{}f}\n", pi, 3, width = 10); // 66. 动态指定浮点数的宽度和精度 std::cout << std::format("67. {:{fill}>{width}}\n", str, fill='-', width=15); // 67. 动态指定字符串右对齐的宽度和填充字符 std::cout << std::format("68. {0:{fill}<{width}}\n", str, fill='-', width=15);// 68. 动态指定字符串左对齐的宽度和填充字符 std::cout << std::format("69. {0:0{1},}\n", 1234567890, 15); // 69. 整数动态指定宽度、千位分隔符 std::cout << std::format("70. {0:s} {1:x}\n", "String", num); // 70. 在同一行输出字符串和小写十六进制整数 std::cout << std::format("71. {:08b}\n", num); // 71. 补零的二进制整数表示 std::cout << std::format("72. {0:+>10} {1:-^10}\n", str, num); // 72. 同时使用填充字符和对齐方式 std::cout << std::format("73. {0:s} {1:+>10}\n", long_str, num); // 73. 不同类型的参数使用不同对齐方式 std::cout << std::format("74. {0:.{1}s}\n", long_str, 10); // 74. 动态指定字符串截断长度 std::cout << std::format("75. {0:<{1}s}\n", long_str, 10); // 75. 动态指定字符串左对齐的宽度 std::cout << std::format("76. {0:.>{1},.{}f}\n", 1234567.89, 15, 2); // 76. 动态指定浮点数宽度、精度和千位分隔符 std::cout << std::format("77. {0:.^20}\n", long_str); // 77. 居中对齐较长的字符串 std::cout << std::format("78. {0:.>{1}}\n", long_str, 30); // 78. 动态指定字符串右对齐的宽度 std::cout << std::format("79. {0:.<{1}}\n", long_str, 30); // 79. 动态指定字符串左对齐的宽度 std::cout << std::format("80. {0:.{1}e}\n", pi, 5); // 80. 动态指定科学计数法的精度 std::cout << std::format("81. {0:.{1}E}\n", pi, 5); // 81. 动态指定科学计数法的精度(大写表示) std::cout << std::format("82. {0:{1}{2}{3}}\n", pi, '>', 10, 'f'); // 82. 分别指定对齐、宽度和格式字符 std::cout << std::format("83. {0:s} {1:+>10.{}f}\n", long_str, pi, 3); // 83. 字符串与带符号的浮点数动态精度 std::cout << std::format("84. {:5}\n", 123); // 84. 整数使用最小宽度 std::cout << std::format("85. {:05}\n", 123); // 85. 整数使用最小宽度和补零 std::cout << std::format("86. {:5}\n", "123"); // 86. 字符串使用最小宽度 std::cout << std::format("87. {:>5}\n", "123"); // 87. 字符串使用最小宽度和右对齐 std::cout << std::format("88. {:^5}\n", "123"); // 88. 字符串使用最小宽度和居中对齐 std::cout << std::format("89. {:<5}\n", "123"); // 89. 字符串使用最小宽度和左对齐 std::cout << std::format("90. {:{}}\n", 123, 5); // 90. 动态指定整数的最小宽度 std::cout << std::format("91. {:{}}\n", "123", 5); // 91. 动态指定字符串的最小宽度 std::cout << std::format("92. {0:s} {1:+0{2}.{}f}\n", long_str, pi, 10, 3); // 92. 字符串与带符号的浮点数动态宽度、精度 std::cout << std::format("93. {0:s} {1:+0{2},.{}f}\n", long_str, 1234567.89, 15, 2); // 93. 字符串与符号的浮点数带小数点 std::cout << std::format("94. {0:#x} {1:#X}\n", num, num); // 94. 十六进制表示,带前缀,分别为小写和大写 std::cout << std::format("95. {0:#o}\n", num); // 95. 带前缀的八进制表示 std::cout << std::format("96. {0:#b} {1:#B}\n", num, num); // 96. 带前缀的二进制表示,分别为小写和大写 std::cout << std::format("97. {0:#0{1}X}\n", num, 6); // 97. 带前缀的十六进制表示,指定宽度和补零 std::cout << std::format("98. {0:#0{1}o}\n", num, 6); // 98. 带前缀的八进制表示,指定宽度和补零 std::cout << std::format("99. {0:#0{1}b}\n", num, 10); // 99. 带前缀的二进制表示,指定宽度和补零 std::cout << std::format("100. {0:s} {1:#0{2}.{}f}\n", long_str, pi, 10, 3); // 100. 字符串与带前缀的浮点数动态宽度、精度 }