前言
Format是一个编程语言非常重要的一环,c++的iomanip里的各种骚操作,在长字符串使用中是一个灾难。因此在c++20中提出了新库formatting,用类似于python的语法实现了fromat。但是目前实际项目中最多到c++17,有的项目甚至在用c++11。还好,fmt库就是formatting。fmt实现了几乎所有C++20的formatting 库,只有非常细微的差别。可以目前使用fmt库,等到项目中使用c++20成熟时,进行如下全局替换:
替换fmt::为std::
替换头文件”fmt/format.h”为<formatting>
fmt可以在github上获取,作为一个fmt的学习,我使用了fmt9.0.1版本。这里只列出了我感兴趣的部分,fmt的帮助非常详尽,可以查看帮助文档。本文中所有示例来自fmt的帮助文档。
安装
纯头文件包含
如果是只使用头文件,不生成动态库,只需要在需要使用的代码中定义如下宏。
FMT_HEADER_ONLY
使用cmake
mkdir build # Create a directory to hold the build output.
cd build
cmake .. # Generate native build scripts.
使用Qt
需要一定的动手能力,新建一个fmt的动态库,删除qt自带的文件,把include,src的文件都包含到工程中。并在pro文件中加入如下:
DEFINES+=FMT_EXPORT
DEFINES+=FMT_SHARED
INCLUDEPATH+=$$PWD/include
使用
基本使用
Fmt的替换字段为{},如果不被大括号包含,则认为是文本。如果需要输出{或},需要敲两遍{{和}}。如果需要要指定某个参数,在大括号中使用整数索引即可,索引从零开始。
先来一个简单的例子:
"First, thou shalt count to {0}" // 引用第一个参数
"Bring me a {}" // 隐式应用第一个参数
"From {} to {}" // 和 "From {0} to {1}"一样
下面是是一个基本的应用。print输出到stdout。
fmt::format("{0}, {1}, {2}", 'a', 'b', 'c');
// Result: "a, b, c"
fmt::format("{}, {}, {}", 'a', 'b', 'c');
// Result: "a, b, c"
fmt::format("{2}, {1}, {0}", 'a', 'b', 'c');
// Result: "c, b, a"
fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated
// Result: "abracadabra"
fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
fmt::format("{}", fmt::ptr(p));
// Result: "0x7f1416f888"
命名参数
可以对参数进行命名,使用arg。
fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
当然,也可以利用自定义字面量,等同于arg。
using namespace fmt::literals;
fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
设定格式
fmt的格式化和c语言的printf非常相似,printf使用%分割,fmt使用了{}和:。比如"%03.2f"可以替换为"{:03.2f}"。下面是对齐文本和设定宽度的例子。
fmt::format("{:<30}", "left aligned");
// Result: "left aligned "
fmt::format("{:>30}", "right aligned");
// Result: " right aligned"
fmt::format("{:^30}", "centered");
// Result: " centered "
fmt::format("{:*^30}", "centered"); // use '*' as a fill char
// Result: "***********centered***********"
特殊格式控制如下:
'<' | 左对齐,对象默认左对齐 |
'>' | 右对齐 (数字默认右对齐)。 |
'^' | 居中。 |
's' | 字符串。 |
'c' | 字符 |
'p' | 指针 |
整数
整数的格式如下:
'b' | 二进制 |
'B' | 二进制 |
'c' | 按字符输出 |
'd' | 十进制 |
'o' | 八进制 |
'x' | 十六进制 |
'X' | 十六进制 |
none | 和'd'一样 |
通过{:d},{:x},{:o}和{:b}来控制十进制、16进制、8进制、二进制输出。这里使用#可以输出前缀。使用{:#x}会输出0x,输出小写字符,使用{:#b}会输出0b。使用{:#X}会输出0X,输出大写字符,使用{:#B}会输出0B。
fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
// Result: "int: 42; hex: 2a; oct: 52; bin: 101010"
// with 0x or 0 or 0b as prefix:
fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42);
// Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010"
Padded hex byte with prefix and always prints both hex characters:
fmt::format("{:#04x}", 0);
// Result: "0x00"
浮点数
浮点数格式如下:
'a' | 十六进制输出,有前缀0x,小写字符。使用’p’来表示指数 |
'A' | 和’a’一样,只是输出为大写字符。 |
'e' | 科学计数法,使用‘e’来表示指数部分。 |
'E' | 和'e'一样,但是分隔符为大写'E' |
'f' | 定点输出。 |
'F' | 和'f'一样, 但是会把nan替换为NAN,inf替换为INF。 |
'g' | 如果精度p>=1, 会圆整到该精度。会根据简洁性选择定点或浮点输出。 |
'G' | 和'g'一样,但是会大写。 |
none | 和'g'一样, 但是默认精度为能表达值的最大精度 |
当宽度需要使用变量来设定时,注意嵌套的{}。
fmt::format("{:<{}}", "left aligned", 30);
// Result: "left aligned "
当精度需要使用变量来设定时,注意嵌套的{}。
fmt::format("{:.{}f}", 3.14, 1);
// Result: "3.1"
使用{:+f },{:-f},{ f}设定符号位。
fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always
// Result: "+3.140000; -3.140000"
fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers
// Result: " 3.140000; -3.140000"
fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}'
// Result: "3.140000; -3.140000"
使用Unicode绘制方框
fmt::print(
"┌{0:─^{2}}┐\n"
"│{1: ^{2}}│\n"
"└{0:─^{2}}┘\n", "", "Hello, world!", 20);
结果如下:
┌────────────────────┐
│ Hello, world! │
└────────────────────┘
日期
支持如下类型
std::chrono::duration
std::chrono::time_point
std::tm
直接支持tm结构体
#include <fmt/chrono.h>
auto t = tm();
t.tm_year = 2010 - 1900;
t.tm_mon = 7;
t.tm_mday = 4;
t.tm_hour = 12;
t.tm_min = 15;
t.tm_sec = 58;
fmt::print("{:%Y-%m-%d %H:%M:%S}", t);
// Prints: 2010-08-04 12:15:58
fmt提供了localtime,gmtime等转换函数可以转换time_t到tm。
#include <fmt/chrono.h>
int main() {
std::time_t t = std::time(nullptr);
// Prints "The date is 2020-11-07." (with the current date):
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
using namespace std::literals::chrono_literals;
// Prints "Default format: 42s 100ms":
fmt::print("Default format: {} {}\n", 42s, 100ms);
// Prints "strftime-like format: 03:15:30":
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
}
Locale
当然支持设置locale,根据不同的语言环境,输出符合语言习惯的格式。
#include <fmt/format.h>
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
// s == "1,234,567,890"
Tuple
支持tuple输出
#include <fmt/ranges.h>
std::tuple<char, int, float> t{'a', 1, 2.0f};
// Prints "('a', 1, 2.0)"
fmt::print("{}", t);
ranges
支持对ranges的支持
fmt::format("{}", std::vector{10, 20, 30});
// Result: [10, 20, 30]
fmt::format("{::#x}", std::vector{10, 20, 30});
// Result: [0xa, 0x14, 0x13]
fmt::format("{}", vector{'h', 'e', 'l', 'l', 'o'});
// Result: ['h', 'e', 'l', 'l', 'o']
fmt::format("{::}", vector{'h', 'e', 'l', 'l', 'o'});
// Result: [h, e, l, l, o]
fmt::format("{::d}", vector{'h', 'e', 'l', 'l', 'o'});
// Result: [104, 101, 108, 108, 111]
有时候我们的需要把容器中的数据采用分隔符分开,这时候可以使用join
std::vector<int> v = {1, 2, 3};
fmt::print("{}", fmt::join(v, ", "));
// Output: "1, 2, 3"
枚举
#include <fmt/format.h>
namespace kevin_namespacy {
enum class film {
house_of_cards, american_beauty, se7en = 7
};
auto format_as(film f) { return fmt::underlying(f); }
}
int main() {
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
}
标准库类型
支持如下类型
std::filesystem::path
std::thread::id
std::monostate
std::variant
下面是简单的示例
#include <fmt/std.h>
std::variant<char, float> v0{'x'};
// Prints "variant('x')"
fmt::print("{}", v0);
std::variant<std::monostate, char> v1;
// Prints "variant(monostate)"
编译期格式化
使用宏FMT_COMPILE包含格式化字符串,如果编译期支持 C++17的constexpr if,那么将会进行编译期的格式化。
// Converts 42 into std::string using the most efficient method and no
// runtime format string processing.
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
自定义类型
实际项目中,自定义类型是绕不过去的类型,支持两种方式。一种是特化fmt::formatter类型,如下所示。
#include <fmt/format.h>
struct point {
double x, y;
};
template <> struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential.
char presentation = 'f';
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Please also note that this character range may be empty, in case of
// the "{}" format string, so therefore you should check ctx.begin()
// for equality with ctx.end().
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
// Check if reached the end of the range:
if (it != end && *it != '}') throw format_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
// ctx.out() is an output iterator to write to.
return presentation == 'f'
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
: fmt::format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
}
};
有了上面的铺垫,我们就可以直接使用point类了。
point p = {1, 2};
std::string s = fmt::format("{:f}", p);
// s == "(1.0, 2.0)"
另一种方法是利用对std::ostream的支持,如下所示。
#include <fmt/ostream.h>
struct date {
int year, month, day;
friend std::ostream& operator<<(std::ostream& os, const date& d) {
return os << d.year << '-' << d.month << '-' << d.day;
}
};
template <> struct fmt::formatter<date> : ostream_formatter {};
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
// s == "The date is 2012-12-9"
终端颜色和格式
fg前景色,bg背景色。
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
fmt::print("Elapsed time: {0:.2f} seconds",
fmt::styled(1.23, fmt::fg(fmt::color::green) |
fmt::bg(fmt::color::blue)));
FILE对象
fmt::print(stderr, "Don't {}!", "panic");
输出到文件
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");