c++20 formatting(fmt)使用方法

前言

Format是一个编程语言非常重要的一环,c++的iomanip里的各种骚操作,在长字符串使用中是一个灾难。因此在c++20中提出了新库formatting,用类似于python的语法实现了fromat。但是目前实际项目中最多到c++17,有的项目甚至在用c++11。还好,fmt库就是formatting。fmt实现了几乎所有C++20的formatting 库,只有非常细微的差别。可以目前使用fmt库,等到项目中使用c++20成熟时,进行如下全局替换:

  1. 替换fmt::为std::

  1. 替换头文件”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");

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值