在之前的C++标准之中,如果你想格式化文本,你可以使用传统的printf函数或STL iostream库,但是这两者,各有优缺点。
printf函数继承自C语言,50多年的发展,已经让其很高效,灵活和方便。就是格式语法看起来有点晦涩,但习惯后感觉还行。
printf("Hello,%s\n",c_string);
printf的缺点就是弱类型安全。printf函数,使用C的可变参数模型将参数传递给格式化程序。如果正常运行那么会非常高效,但参数类型与其对应的格式说明符不匹配时,可能会产生严重问题。
STL的iostream库以可读性和运行时性能为代价确保了类型安全。iostream的语法不常见,但很简单易懂。
cout<<"Hello,"<
iostream的缺点在于语法和实现方面的复杂性,构建格式化字符串可能冗长而晦涩。许多格式操作符在使用后必须重置,非则会产生难以调试的级联格式错误。这个库的本身庞大而复杂,导致代码比printf等效代码大太多,速度也慢很多。
最终的结果是,C++程序员只能在者两种有缺陷的方法中选择一种。
新格式库位于
format()函数接受一个string_view格式的字符串和一个可变参数参数包,并返回一个字符串。其函数签名为:
template
string format(string_view fmt,const Args&...args);
format()返回类型或值的字符串表现形式。如下
string who{"everyone""};
int ival{42};
double pi{std::numbers::pi};
format("Hello, {}!\n",who); //Hello, everyone!
format("Integer: {}\n",ival); //Integer: 42
format("Π: {}\n",pi); //Π: 3.141592653589793
格式化字符串使用大括号{}作为类型安全的占位符,可以将任何兼容类型的值转换为合理的字符串表现形式
可以在格式字符串中包含多个占位符:
format("Hello {} {}",ival,who); //Hello 42 everyone
可以指定替换值的顺序
format("Hello {1} {0}",ival,who);//Hello everyone 42
format("Hello {0} {1}",ival,who);//Hello 42 everyone
这也可以进行对齐,左(<),右(>)或中心(^)对齐,可以选择性使用填充字符:
format("{:.<10}",ival); //42........
format("{:.>10}",ival); //........42
format("{:.^10}",ival); //....42....
也可以设置十进制数值的精度
format("Π:{:.5}",pi); //Π: 3.1416
这是一个丰富而完整的格式化方式,具有iostream的类型安全,已经printf的性能和简单性,达到了鱼和熊掌兼得的目的
format()函数本身返回一个字符串对象。若想打印字符串,需要使用iostream或cstdio
cout<
这两种方法都不理想(毕竟还要调用除format以外的函数),但是编写一个简单的print()函数并不难。在这一个过程中来了解一些格式库的工作方式。下面提供了print()函数使用格式库的简单实现
#include
#include
#include
template
void print(const string_view fmt_str,Args&&...args){
auto fmt_args{make_format_args(args...)};
string outstr{vformat(fmt_str,fmt_args)};
fputs(outstr.c_str(),stdout);
}
注:make_format_args()函数的作用:接受参数包并返回一个对象,该对象包含适合格式化的已擦除类型的值。然后,将该对象传递给vformat(),vformat()再返回合适打印的字符串。再使用fputs()将值输出到控制台上。
现在可以使用print()函数,来代替cout< 输出为: 另外的类似的print()函数,这也是C++23计划的一部分。到时后编译器支持C++23的print()时,使用std::print就能完成所有工作. 如下,这里有两个成员的简答结构体:分子和分母。将其输出为分数: 编译时,会遇到如"没有定义的转换运算符..."等一系列错误. 当格式化系统遇到要转换的对象时,其会寻找具有相应类型的格式化程序对象的特化。因此我们也要建立一个对应自定义类型的特化。 格式化特化,是具有两个简短模板模板函数的类 prase()函数解析格式字符串,从冒号之后(若没有冒号,则在开大括号之后)直到但不包括结束大括号。 format()函数接受一个Frac对象和一个FormatContext对象,返回结束迭代器。format_to()函数可使这变得很容易。先将f.n和f.d放入string_view即"{0:d}/{1:d}"中去,然后再将结果放入到目标格式化字符串中去。 现在有了Frac的特化,可以将对象传递print()从而获得一个可读的结果: 输出为 C++20通过提供高效.方便的类型安全文本格式库,解决了一个长期存在的问题。 参考书籍《C++20 cookbook》print("Hello, {}!\n",who);
print("Π: {}\n",pi);
print("Hello, {1} {0}!\n",ival,who);
print("{:.^10}\n",ival);
print("{:5}\n",pi);
Hello, everyone!
Π: 3.141592653589793
Hello everyone 42
....42....
3.1416
format处理自定义类型
struct Frac{
long n;
long d;
}
int main(){
Frac f{5,3};
print("Frac: {}\n",f);
}
template<>
struct std::formatter
Frac: 5/3