> 文章列表 > 现代C++格式化:拥抱std--format简化你的代码

现代C++格式化:拥抱std--format简化你的代码

现代C++格式化:拥抱std--format简化你的代码

现代C++格式化:拥抱std::format简化你的代码

  • 1. 引言
    • 传统C++格式化的问题与挑战
    • C++20引入std::format的背景
  • 2. std::format简介
      • std::format的基本概念
      • std::format与printf、iostreams的对比
    • 高效使用std::format的理由
  • 3. 基本用法
  • 4. 格式化数字
    • 控制数字的宽度、精度与填充
    • 显示或隐藏正负号
    • 进制转换(十进制、十六进制、八进制等)
    • 浮点数格式化选项
  • 5. 格式化文本
    • 控制字符串的宽度与填充
    • 处理特殊字符与转义
    • 使用std::format处理多语言与Unicode
  • 6. 格式化日期与时间
    • 使用chrono库处理时间点与持续时间
    • 时间格式化选项
    • 本地化日期与时间的显示
  • 7. 自定义类型的格式化
    • 实现自定义类型的格式化支持
    • 使用fmt::formatter特化
    • 示例:为自定义类型实现格式化输出
  • 8. std::format的高级技巧与应用
    • 格式字符串的动态生成
    • 使用std::format与其他标准库组件(如容器、文件操作等)
    • 提高格式化性能的建议
  • 9. 结论与展望
    • std::format在现代C++中的地位与作用
    • 与其他语言的格式化库的比较
    • C++标准化进程中格式化相关的未来发展
  • 10.一百个std::format使用示例

1. 引言

在本文中,我们将详细讨论C++格式化的传统问题与挑战,以及C++20标准中引入std::format的背景。

传统C++格式化的问题与挑战

传统C++格式化存在一些问题与挑战,主要包括以下几点:

  1. 可读性差:使用C++中的printfscanf家族函数进行格式化输出和输入时,它们的语法较为复杂,难以阅读。在较大的代码项目中,可读性差会导致维护困难。
  2. 类型安全性差printfscanf等函数无法在编译期间检查参数的类型是否正确。这可能导致运行时错误,甚至引发程序崩溃。
  3. 不够灵活:对于复杂的格式化需求,printfscanf等函数提供的功能有限。例如,它们不支持自定义类型的格式化,也不方便处理宽字符和多字节字符集。
  4. 性能开销:由于传统的格式化方法在运行时需要处理格式字符串,它们可能导致额外的性能开销。

C++20引入std::format的背景

鉴于传统C++格式化方法的局限性,C++20标准中引入了std::format库,旨在提供一种更现代、更安全、更灵活的格式化方法。引入std::format的主要动机包括:

  1. 提高可读性std::format采用了一种更加简洁、易懂的语法,使得格式化字符串更具可读性。
  2. 增强类型安全std::format在编译期间就可以检查参数类型的正确性,从而降低运行时错误的风险。
  3. 扩展功能std::format支持自定义类型的格式化,同时兼容宽字符和多字节字符集。这使得开发人员能够满足更为复杂的格式化需求。
  4. 性能优化std::format设计时充分考虑了性能问题,相比传统的格式化方法,它在许多场景下能够提供更高的性能。

总之,std::format作为C++20标准的一部分,旨在解决传统C++格式化方法的问题,并为开发者提供一种更现代、更安全、更灵活的格式化工具。

2. std::format简介

在本节中,我们将简要介绍std::format的基本概念,并对比std::formatprintfiostreams之间的差异。

std::format的基本概念

std::format是C++20标准库中新增的一个格式化工具,它基于Python中的str.format()函数,提供了一种类型安全且易于阅读的字符串格式化方法。std::format的主要特点包括:

  1. 替换字段std::format使用花括号{}作为替换字段的占位符。这些替换字段在格式化时会被相应的参数值替换。
  2. 格式规范std::format支持在替换字段内部定义格式规范,例如指定输出宽度、对齐方式和填充字符等。格式规范使用冒号:分隔,放在花括号内。
  3. 编译时类型检查std::format在编译期间检查参数类型的正确性,以提高类型安全性。
  4. 自定义类型支持std::format可以通过重载formatter特化来支持自定义类型的格式化。

std::format与printf、iostreams的对比

下面我们将对比std::formatprintfiostreams之间的主要差异:

  1. 可读性std::format使用花括号作为占位符,并允许在占位符内定义格式规范。这使得格式化字符串更具可读性,相较于printfiostreams更为简洁明了。

    示例:

    std::cout << std::format("Hello, {}!\\n", "World"); // std::format
    printf("Hello, %s!\\n", "World");                  // printf
    std::cout << "Hello, " << "World" << "!\\n";       // iostreams
  2. 类型安全std::format在编译期间检查参数类型的正确性,而printf在运行时检查类型。iostreams也具有类型安全性,但std::format更接近printf的语法,使得从printf迁移到std::format更容易。

  3. 扩展性std::format支持自定义类型的格式化,而printf仅支持内置类型。iostreams通过重载插入和提取操作符支持自定义类型,但std::format提供更为统一的扩展方法。

  4. 性能std::format在设计时充分考虑了性能问题,因此在许多场景下性能优于iostreams。而与printf相比,std::format的性能表现也非常出色。

综上所述,std::format在可读性、类型安全性、扩展性和性能方面都表现优异,成为现代C++编程中推荐的字符串格式化工具。

高效使用std::format的理由

以下是为什么应该高效使用std::format的几个理由:

  1. 统一的格式化语法std::format提供了一种统一的格式化语法,无论是内置类型还是自定义类型,都可以使用相同的方法进行格式化。这有助于简化代码并降低维护成本。
  2. 简化代码:由于std::format提供了更简洁的语法,使用它可以减少代码量,使代码更易于理解。相较于printfiostreamsstd::format更适合处理复杂的字符串格式化需求。
  3. 避免运行时错误std::format在编译期间检查参数类型,能够减少因类型错误导致的运行时错误。这有助于提高代码的健壮性和稳定性。
  4. 易于迁移:对于已经习惯使用printf的开发者,std::format提供了类似的语法和功能,可以轻松从printf迁移到std::format
  5. 便于调试和优化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支持各种类型规格与格式选项,以便对输出进行详细的控制。以下是一些常见的类型规格与格式选项:

  1. 整数

    • d:十进制整数。
    • x:小写十六进制整数。
    • X:大写十六进制整数。
    • o:八进制整数。
    • b:二进制整数。
      示例:
    std::cout << std::format("{0:d} {0:x} {0:X} {0:o} {0:b}\\n", 42);
  2. 浮点数

    • f:固定点表示法。
    • e:小写科学计数法。
    • E:大写科学计数法。
    • g:根据值选择最简表示法(fe)。
    • G:根据值选择最简表示法(fE)。
      示例:
    std::cout << std::format("{0:f} {0:e} {0:E} {0:g} {0:G}\\n", 3.1415926535);
  3. 字符串

    • s:字符串。
      示例:
    std::cout << std::format("{:s}\\n", "Hello, World!");
  4. 宽度、对齐和填充

    • <:左对齐。
    • >:右对齐。
    • ^:居中对齐。
    • 数字:指定输出宽度。
    • 字符:指定填充字符。
      示例:
    std::cout << std::format("{:<10} | {:>10} | {:^10}\\n", "left", "right", "center");
    std::cout << std::format("{:*<10} | {:#>10} | {:_^10}\\n", "left", "right", "center");
  5. 精度

    对于浮点数,精度用于指定小数点后的位数;对于字符串,精度用于指定最大输出长度。

    示例:

    std::cout << std::format("{:.2f} | {:.3e} | {:.4s}\\n", 3.1415926, 12345.6789, "abcdefgh");
    
  6. 整数和浮点数的进位

    整数和浮点数的进位可以使用#选项,它会在八进制和十六进制数字前添加00x0X)前缀,或在浮点数上强制输出小数点。

    示例:

    std::cout << std::format("{:#x} | {:#o} | {:#f}\\n", 42, 42, 3.14);
  7. 正负号

    使用+选项可以强制输出正数的正号。

    示例:

    std::cout << std::format("{:+d} | {:+f}\\n", 42, 3.14);
  8. 自定义类型

    : 要格式化自定义类型,需要为类型特化std::formatter模板,并提供parseformat成员函数。这使得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_pointsteady_clock::time_pointduration等。要使用std::format格式化这些类型,首先需要包含<chrono><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结构,并包含<iomanip>头文件。以下是一个示例:

#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()成员函数。

以下是实现自定义类型格式化输出的步骤:

  1. 包含<format>头文件。
  2. 为您的自定义类型特化std::formatter
  3. 在特化的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在很多方面都比传统的格式化方法更高效,但在某些情况下,性能仍然是一个值得关注的问题。以下是一些建议,可以帮助您提高格式化性能:

  1. 避免频繁构建和销毁格式化字符串:在循环或高频调用的函数中避免重复构建格式化字符串。考虑将格式化字符串预先计算并存储为常量或静态变量。
  2. 减少不必要的字符串连接:在可能的情况下,尽量避免使用+运算符连接字符串。可以使用std::format直接构建最终字符串,而不是分段拼接。例如,可以将多个std::format`调用替换为一个带有多个占位符的调用。
  3. 使用预分配的内存:为频繁使用的字符串分配足够的预先分配的内存,以减少内存分配和重新分配的开销。例如,您可以使用std::string::reserve()函数为字符串预留足够的空间。
  4. 避免不必要的类型转换:在可能的情况下,尽量避免在格式化之前将数据类型转换为其他类型。例如,不要在格式化之前将int转换为std::string,而是直接使用int类型的格式规范。
  5. 选择合适的容器和算法:根据具体应用场景选择合适的容器和算法,以实现最佳性能。例如,对于需要快速插入和删除元素的场景,使用std::liststd::deque而不是std::vector

通过遵循以上建议,您可以确保在使用std::format进行格式化操作时实现最佳性能。这将有助于提高您的C++应用程序的整体性能和响应速度。

9. 结论与展望

std::format在现代C++中的地位与作用

std::format是C++20中引入的一个重要特性,它在现代C++中扮演着重要的角色。与传统的C++格式化方法相比,如printfiostreamsstd::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时显示字母Lstd::cout << std::format("29. {:P}\\n", false);                                // 29. 布尔值为false时显示字母Pstd::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. 字符串与带前缀的浮点数动态宽度、精度
}