fmt 是一个开源、轻量、高性能的格式化库,实现了 C++20的std::format标准 ,用来替代C中stdio和C++的iostreams。fmt的官网是 https://fmt.dev,Github代码库链接为:https://github.com/fmtlib/fmt

本文简要介绍fmt的用法,以及对格式化语法做一些说明。

fmt用法示例

相对于 (s)printfcout/cerrstringstream等,fmt使用上非常简单,直接一个大括号就能格式化/输出整数、浮点数、布尔、指针、容器等绝大多数内置类型数据:

#include <fmt/core.h>
#include <fmt/ranges.h>
#include <iostream>
#include <vector>

int main() {
  // 字符串
  auto name = fmt::format("Hello, {}\n", "tlanyan");
  std::cout << name; // 输出 Hello, tlanyan
  // 或者直接使用fmt::print
  fmt::print("Hello, {}\n", "tlanyan");

  // 整数
  fmt::print("The answer is {}\n", 42); // 输出 The answer is 42

  // 浮点数
  fmt::print("PI is {:.2f}\n", 3.14159); // 输出 PI is 3.14

  // 布尔
  fmt::print("Are you human being? {}\n", true); // 输出Are you human being? true

  // vector
  // 需要 #include <fmt/ranges.h>
  fmt::print("vec: {}\n", std::vector{1, 4, 7}); // 输出 vec: [1, 4, 7]
  
  return 0;
}

fmt::formatfmt::printfmt库中最常用到的两个接口。和可以看到,只需简单的引入fmt,就可以像 Python 一样方便的进行字符串格式化或者输出,并且格式和可读性都满足要求。

除了上面简单的进行格式化或者输出,fmt 还可以输出到文件:

// 输出到文件需要包含该头文件
#include <fmt/os.h>

int main() {
  auto fout = fmt::output_file("readme.txt");
  fout.print("{} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams.\n", "fmt");

  return 0;
}

以及支持颜色和字体风格输出:

// 彩色输出需要包含该头文件
#include <fmt/color.h>

int main() {
  fmt::print("info\n");
  fmt::print(fg(fmt::color::green), "success\n");
  fmt::print(fg(fmt::color::orange) | fmt::emphasis::underline, "warning\n");
  fmt::print(fg(fmt::color::red) | fmt::emphasis::bold | fmt::emphasis::underline, "danger\n");;

  return 0;
}

效果如下:

fmt color output
fmt color output

fmt格式化语法

默认情况下,fmt已经足够使用。如果有更高级的需求,则应该了解fmt的格式化语法。

fmt::formatfmt::print 函数中字符串模版的 {} 是格式化占位符,其完整形式是:

{参数id:格式化选项}

其中参数id和格式化选项都是可选的,两者可同时(不)出现,也可只出现一个。

参数id

参数id一般是从0开始的整数,不写的情况下fmt会按照出现的顺序替换成{0}、{1}等等。也可手动指定该位置使用的参数:

// 不指定参数id
fmt::print("{} + {} = {}\n", 1, 1, 2); // 输出 1 + 1 = 2
// 指定参数id
fmt::print("{0} + {0} = {1}\n", 1, 2); // 输出 1 + 1 = 2
fmt::print("{1} is bigger than {0}\n", 12, 25); // 输出 25 is bigger than 12

除了使用整数,fmt也支持使用更语义化的命名字符串/别名作为参数id:

fmt::print("{first} + {second} = {sum}\n", fmt::arg("first", 1), fmt::arg("second", 2), fmt::arg("sum", 3));
// 或者使用C++11中的字面量
#include <fmt/format.h>
using namespace fmt::literals;
fmt::print("{first} + {second} = {sum}\n", "first"_a=1, "second"_a=2, "sum"_a=3);

其中命名字符串的命名规则同变量,以字母或者下划线开始,可以包含数字。

格式化选项

相对于参数id,格式化选项比较丰富,因为不同数据类型的格式化选项都不一样,糅合了printf函数中的格式化以及<iomanip>中的宽度、对齐等。其完整形式为:

填充 对齐 符号 # 0 宽度 精度 类型

最常用的是 对齐、宽度、精度和类型的组合,取值范围:

  • 对齐< 靠左对齐,> 靠右对齐,^ 居中对齐
  • 宽度:整数,例如20表示占据20个字符
  • 精度:以 . 开始的数字,用于浮点数,例如 .6 展示小数点后6位
  • 类型:基本上同printf中的类型,d表示整数、f/F/g/G/e/E表示浮点数,s表示字符串,p表示指针,b/B/o/x/X用二/八/十六进制表示整数

一个示例:

// id的值占据10位,靠右对齐,是整数
// value的值占据10位,靠右对齐,保留小数点后4位,是浮点数
fmt::print("id:{:<10d}value{:>10.4f}\n", 1, 3.14159267);

除了这些基本类型,fmt对时间格式有单独支持,具体请参考官方文档,这里仅给出一个示例:

// 需包含该头文件
// #include <fmt/chrono.h>

std::time_t t = std::time(nullptr);
fmt::print("{:%Y-%m-%d %H:%M:%S}\n", fmt::localtime(t)); // 输出 2023-10-08 09:05:36

fmt对支持的每个数据类型都内置了格式化选项,一般来说无需设置格式化选项。

总结

非常推荐在项目中使用,让格式化相关代码可读性大大提升。