C++ `std::format`:C++20 安全、高效的文本格式化

好的,各位朋友,欢迎来到“C++ std::format:C++20 安全、高效的文本格式化”讲座现场!今天咱们就来好好聊聊C++20引入的这个神器——std::format

开场白:告别printf,拥抱现代C++

相信大家都或多或少用过C语言时代的printf函数,它确实很强大,能把各种数据按照指定的格式打印出来。但是,老朋友也有老朋友的烦恼,printf最让人头疼的就是类型安全问题。一不小心,格式字符串和参数类型不匹配,轻则输出乱码,重则程序崩溃!

int age = 30;
printf("我今年%s岁了。n", age); // 绝对的灾难!

上面这段代码,格式字符串用%s来表示字符串,但实际传入的是int类型的age,编译器不会报错,但运行起来就等着崩溃吧。

C++也提供了std::iostream,但它的格式化操作符(<<)用起来略显繁琐,而且自定义格式化也比较麻烦。

所以,C++20横空出世,带来了std::format,它既有printf的强大,又解决了类型安全问题,还拥有更好的性能。这简直就是程序员的福音啊!

std::format:闪亮登场!

std::format位于<format>头文件中,使用起来非常简单。它使用花括号{}作为占位符,并在花括号内指定格式。

最简单的例子:

#include <iostream>
#include <format>

int main() {
  std::string name = "张三";
  int age = 30;

  std::cout << std::format("你好,我是{},今年{}岁。n", name, age);
  return 0;
}

输出结果:

你好,我是张三,今年30岁。

看到了吗?std::format会自动根据参数的类型进行格式化,不需要我们手动指定类型,也不用担心类型不匹配的问题。

格式化说明符:精雕细琢你的输出

std::format的强大之处在于它提供了丰富的格式化说明符,可以对输出进行精细的控制。格式化说明符放在花括号{}内,冒号:之后。

1. 填充与对齐

  • 填充字符: 可以指定一个字符来填充空白区域,例如空格、0等。
  • 对齐方式:
    • <:左对齐
    • >:右对齐
    • ^:居中对齐
#include <iostream>
#include <format>

int main() {
  int num = 42;

  std::cout << std::format("{:5d}n", num);      // 默认右对齐,填充空格
  std::cout << std::format("{:<5d}n", num);     // 左对齐,填充空格
  std::cout << std::format("{:>5d}n", num);     // 右对齐,填充空格
  std::cout << std::format("{:^5d}n", num);     // 居中对齐,填充空格
  std::cout << std::format("{:05d}n", num);     // 右对齐,填充0
  std::cout << std::format("{:*^5d}n", num);    // 居中对齐,填充*
  return 0;
}

输出结果:

   42
42   
   42
  42 
00042
**42*

2. 数字格式化

  • 整数类型:

    • d:十进制整数(默认)
    • b:二进制整数
    • o:八进制整数
    • x:十六进制整数(小写字母)
    • X:十六进制整数(大写字母)
  • 浮点数类型:

    • f:定点表示法
    • e:科学计数法(小写字母)
    • E:科学计数法(大写字母)
    • g:通用格式(自动选择fe
    • G:通用格式(自动选择fE
    • %:百分比表示法
  • 精度: 使用.后跟数字来指定精度。对于浮点数,表示小数点后的位数;对于整数,表示最小位数。

  • 千位分隔符: 使用,可以添加千位分隔符。

#include <iostream>
#include <format>
#include <cmath>

int main() {
  int num = 255;
  double pi = std::numbers::pi;

  std::cout << std::format("十进制:{}n", num);
  std::cout << std::format("二进制:{:b}n", num);
  std::cout << std::format("八进制:{:o}n", num);
  std::cout << std::format("十六进制(小写):{:x}n", num);
  std::cout << std::format("十六进制(大写):{:X}n", num);

  std::cout << std::format("Pi: {}n", pi);
  std::cout << std::format("Pi: {:.2f}n", pi);    // 保留两位小数
  std::cout << std::format("Pi: {:e}n", pi);      // 科学计数法
  std::cout << std::format("Pi: {:.2e}n", pi);    // 科学计数法,保留两位小数
  std::cout << std::format("Pi: {:g}n", pi);      // 通用格式

  int large_num = 1234567;
  std::cout << std::format("带千位分隔符:{:,d}n", large_num);

  double percentage = 0.85;
  std::cout << std::format("百分比:{:.2%}n", percentage); // 转换为百分比,保留两位小数
  return 0;
}

输出结果:

十进制:255
二进制:11111111
八进制:377
十六进制(小写):ff
十六进制(大写):FF
Pi: 3.14159
Pi: 3.14
Pi: 3.141593e+00
Pi: 3.14e+00
Pi: 3.14159
带千位分隔符:1,234,567
百分比:85.00%

3. 字符与字符串格式化

  • s:字符串(默认)
  • c:字符
#include <iostream>
#include <format>

int main() {
  char ch = 'A';
  std::string str = "Hello";

  std::cout << std::format("字符:{}n", ch);
  std::cout << std::format("字符串:{}n", str);
  std::cout << std::format("字符串(宽度):{:10}n", str); // 宽度为10,默认右对齐
  std::cout << std::format("字符串(左对齐):{:<10}n", str); // 宽度为10,左对齐
  return 0;
}

输出结果:

字符:A
字符串:Hello
字符串(宽度):     Hello
字符串(左对齐):Hello

4. 日期与时间格式化

std::format也支持日期和时间的格式化,需要包含<chrono>头文件。但是,C++20对chrono的格式化支持比较有限,C++23将会引入更强大的支持。这里简单演示一下:

#include <iostream>
#include <format>
#include <chrono>

int main() {
  auto now = std::chrono::system_clock::now();
  auto time_point = std::chrono::system_clock::to_time_t(now);
  std::tm local_time = *std::localtime(&time_point); // 转换为本地时间

  std::cout << std::format("日期时间:{:%Y-%m-%d %H:%M:%S}n", local_time);
  return 0;
}

输出结果(取决于当前时间):

日期时间:2023-10-27 10:30:00

注意: chrono的格式化说明符以%开头,例如%Y表示年份,%m表示月份,%d表示日期,%H表示小时,%M表示分钟,%S表示秒。

std::vformat:灵活的格式化

std::vformatstd::format的底层函数,它接受一个std::format_args对象作为参数,这个对象包含了要格式化的参数列表。std::format实际上就是调用std::vformat来实现格式化的。

使用std::vformat可以实现更灵活的格式化,例如在运行时动态构建格式字符串。

#include <iostream>
#include <format>

std::string format_message(const std::string& format_str, std::format_args args) {
  return std::vformat(format_str, args);
}

int main() {
  std::string name = "李四";
  int score = 95;

  std::string message = format_message("学生{}的成绩是{}分。n", std::make_format_args(name, score));
  std::cout << message;
  return 0;
}

输出结果:

学生李四的成绩是95分。

性能:std::format的优势

std::format在性能方面通常优于printfstd::iostream

  • 编译时检查: std::format可以在编译时检查格式字符串的有效性,避免运行时错误。
  • 减少内存分配: std::format通常可以避免不必要的内存分配,提高性能。
  • 优化实现: 现代C++编译器的std::format实现都经过了高度优化。

总结:std::format,你值得拥有!

std::format是C++20引入的一个强大的文本格式化工具,它具有以下优点:

  • 类型安全: 避免了printf的类型安全问题。
  • 简单易用: 使用花括号{}作为占位符,语法简洁明了。
  • 功能强大: 提供了丰富的格式化说明符,可以对输出进行精细的控制。
  • 性能优秀: 通常优于printfstd::iostream
  • 可扩展性: 可以自定义格式化器来支持自定义类型。
特性 printf std::iostream std::format
类型安全 部分支持
语法 复杂 繁琐 简洁
功能 强大 一般 强大
性能 较好 一般 优秀
可扩展性 较好
编译时检查

总而言之,std::format是现代C++文本格式化的首选方案。如果你还在使用printf或者觉得std::iostream不够方便,那么赶紧拥抱std::format吧!它会让你写出更安全、更高效、更优雅的代码。

扩展:自定义格式化

std::format还支持自定义类型的格式化。你需要为你的类型提供一个格式化器,这个格式化器负责将你的类型转换为字符串。这部分比较高级,我们这里简单提一下,以后有机会再详细讲解。

结束语:感谢聆听!

好了,今天的“C++ std::format:C++20 安全、高效的文本格式化”讲座就到这里了。希望大家通过今天的学习,能够掌握std::format的基本用法,并在实际项目中灵活运用它。感谢各位的聆听,我们下次再见!希望大家多多练习,熟能生巧!记住,编码的乐趣在于不断探索和学习!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注