C++20 format格式化输出
在C++20之前,格式化能力都依赖于三方格式化库FMT
, 而C++20 标准委员会终于在C++标准库引入了格式化功能,从使用方式和风格来看其实就是FMT
库转正了
直接使用
包含<format.h>
头文件既可以直接使用,类似python
使用{}
作为占位符,{}
会被指定的内容替换
- 输出内置类型
#include<format> std::cout << std::format("hello {}", "world") << std::endl; // 输出 hello world std::cout << std::format("int: {}, bool: {}, double: {},float: {}", 1, true, 1.2, 1.1f); // 输出 int: 1, bool: true, double: 1.2,float: 1.1 // wstring 测试 std::wcout << std::format(L"wstring test {}", L"content") << std::endl; // 输出 wstring text conent
- 如果想输出占位符
{}
怎么办, 只需要在外层再套一个{}
即可std::cout << std::format("{{}}") << std::endl; // 输出{}
- 指定顺序输出
如果只使用{}
占位符号,输出的参数将从头到尾逐一替换{}
进行输出,如果需要指定替换顺序,只要在{}
增加序号表明替换顺序// 输出 this is seq test, first, second std::cout << std::format("this is seq test, {1}, {0}", "second", "first") << std::endl;
- 格式化输出
在输出一些整型和浮点数时,我们会有符号输出、位数和对齐的需求,在format
中可以通过下列方式实现- +:始终带符号输出,正数带+,负数带-
- -: 只有负数带-
- 空格: 在正数前面带空格,负数前面带-
int iValue = 10; // 0表示第一个参数,冒号表示格式的开始 std::format("{0:}, {0:+}, {0:-}, {0: }", iValue) << std::endl; // 输出10, +10, 10, 10
- 0: 冒号后面的0表示填充字符,如果输出的字符宽度小于定义宽度,将使用0进行填充,输出值是0将忽略
int iValue = 10; std::cout << std::format("{:06d}", iValue)<< std::endl; // 输出000010
- #: 对参数输出的形式进行替换,比如对于不同进制的整型数据进行输出时,会在输出参数前面用0b,0x,00的形式进行替换
// 输出 0b1010, 012, 0xa std::cout << std::format("{0:#b}, {0:#o}, {0:#x}", iValue) << std::endl;
- 填充与对齐
填充与对齐主要包含以下三个字符,分别是>,<,^。- >:强制输出,右对齐,使用指定符号填充
- <: 强制输出,左对齐,使用指定符号填充
- ^: 可输出中间内容
int iValue = 10; std::cout << std::format("{0:0>6d}, {0:0<6d}, {0:0^6d}", iValue) << std::endl; // 输出000010, 100000, 001000
- 精度与宽度
使用.和位宽可以指定浮点数的精度和宽度float fValue = 3.1415 std::cout << std::format("{0:.2f}, {0:.6f}", fValue); // 输出 3.14, 3.141500
自定义扩展
通过自定义扩展可以让std::format
方法格式化输出自定义类型
- 对std::formatter模板进行模板特化实现,参考cppreference demo的例子
- 特化之后我们需要实现两个模板函数
template<class ParseContext> constexpr ParseContext::iterator parse(ParseContext& ctx)
输出格式解析参数,可以自定义格式,比如'{:#}'
格式template<class FmtContext>FmtContext::iterator format(QuotableString s, FmtContext& ctx) const
对内容进行格式化的函数- 例子
struct Person {std::string name { "hhh" };int age { 18 };bool man { false }; };// 针对person进行模板特化 template <> struct std::formatter<Person> {// 对格式进行解析,这里我们没有定制constexpr auto parse(std::format_parse_context& ctx){auto it = ctx.begin();if (it == ctx.end()) {return it;}if (*it != '}') {throw format_error("invalid param");}return it;}template <class FmtContext>auto format(const Person& person, FmtContext& ctx) const{// 根据我们想要的格式进行输出return std::format_to(ctx.out(), "name is {}, age is {}, sex is {}", person.name, person.age, person.man ? "man" : "woman");} };
- 如果不想实现
parse
函数,我们也可以继承已有的std::formatter类- 例子
struct Person {std::string name { "hhh" };int age { 18 };bool man { false }; };// 继承已有的formatter类 template <> struct std::formatter<Person> : std::formatter<std::string> {template <class FmtContext>auto format(const Person& person, FmtContext& ctx) const{return std::format_to(ctx.out(), "name is {}, age is {}, sex is {}", person.name, person.age, person.man ? "man" : "woman");} };
- 如果自定义类型是模板类该怎么处理
- 例子
template <typename T1, typename T2, typename T3> struct CustomeTypeStruct {T1 pName;T2 iAge;T3 iScore; }; // 特化formatter时也增加上自定义模板类的类型 template <typename T1, typename T2, typename T3, typename CharT> struct std::formatter<CustomeTypeStruct<T1, T2, T3>, CharT> : std::formatter<T1, CharT> {template <class FormatContext>auto format(CustomeTypeStruct<T1, T2, T3>& stu, FormatContext& fc){return std::format_to(fc.out(), "T1:{}, T2:{}, T3:{}", stu.pName, stu.iAge, stu.iScore);} };
参考
https://mp.weixin.qq.com/s/Rll2rKfpj-6xPlcl5nYaYw
https://en.cppreference.com/w/cpp/utility/format/formatter