boost:format ゝ一纸荒年。 2022-04-13 15:08 205阅读 0赞 # 概述 # * C++标准库提供了强大的、富有弹性的输入/输出流处理,使用流可以对输出的格式做各种精确的控制,比如宽度、精度、进制、填充字符、对齐等,新式流输出操作符`<<`可以串联起任意数量的参数,非常自由 * 但C++输入/输出流也不是完美的,精确输出的格式控制要写大量的操控函数,而且会改变流的状态,用完后还需要及时恢复,有时候会显得十分繁琐。因此,还是有很多程序员怀念C语言中经典的printf(),虽然它缺乏类型安全检查,还有其他一些确定,但它语法简单高效,并且被官方的接受和使用,影响深远。 * boost.format库”扬弃“了printf,实现了类似printf()的格式化对象,可以把参数格式化到一个字符串,而且是完全类型安全的 * format位于名字空间boost,需要包含头文件`<boost/format.hpp>`,即: #include<boost/format.hpp> using namespace boost; ## 入门示例 ## cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50; // prints "writing toto, x=40.230 : 50-th try" 格式对象是由格式字符串构造的,然后通过重复调用operator%给出参数。然后,每个参数都被转换为字符串,这些字符串依次根据格式字符串组合为一个字符串。 cout << format("%s:%d+%d=%d\n") % "sum" %1 %2 % (1 + 2) ; format fmt("(%1% + %2%) * %2% = %3%\n"); fmt % 2 % 5; fmt % ((2+5)*5); cout << fmt.str(); ![在这里插入图片描述][20210518164736323.png] 代码里的第一条语句演示了format的最简单用法,使用format(…)构造了一个format临时对象。构造函数的参数是格式化字符串,其语法是我们非常熟悉的标准printf()语法,使用`%x`来指定参数的格式 因为要格式化的参数个数是不确定的,printf()使用了C语言里的可变参数(即参数声明中的省略号),但它是不安全的。format模仿了流操作符`<<`,重载了二元操作符operator%作为参数输入符,它同样可以串联任意数量的参数,因此: format(...) % a % b % c; 可以近似理解成: format(...) << a << b << c; 操作符%把参数逐个的”喂“给format对象,完成对参数的格式化。 最后,format对象支持流输出,可以直接向输出流count输出内部保存的已经格式化好的字符串。 第一条fomat等价的printf: printf("%s:%d+%d=%d\n", "sum", 1, 2, (1+2)); 最后的三行语句演示了format的另一种用法,预先创建一个format格式化对象,它可以被后面的代码多次用于格式化操作。format对象仍然用操作符%来接受被格式化的参数,可以分多次输入(不必一次给全),但是参数的数量必须满足格式化字符串的要求。最后使用format对象的str()成员函数获得已经格式好的字符串向cout流输出。 第二个format的`(%1% + %2%) * %2% = %3%\n`,使用%N%指示参数的工作。相当于: printf("(%d + %d) * %d = %d\n", 2, 5, 5, (2 + 5) * 5); ## 怎么工作的 ## 1. 当你调用format(s)时,其中s是格式字符串,它构造一个对象,该对象解析格式字符串并查找其中的所有指令,并为下一步准备内部结构。 2. 然后,要么马上,比如 cout << format("%2% %1%") % 36 % 77; * 要么稍后 format fmter("%2% %1%"); fmter % 36; fmter % 77; * 将变量输入格式化程序。这些变量被转存到一个内部流中,根据format-string中的给定格式化选项(如果有的话)设置该状态,并且format对象存储最后一步的字符串结果 1. 一旦提供了所有参数,就可以将格式对象转储到流中,或者通过使用str()成员函数或命名空间boost中的自由函数str(const format&)获取其字符串值。结果字符串在格式对象中保持可访问的状态,直到传递另一个参数,此时它被重新初始化。 // fmter是之前创建和提供的参数,它可以打印结果: cout << fmter ; // 你可以取string结果: string s = fmter.str(); // 可能是几次: s = fmter.str( ); // 你也可以一次完成所有步骤: cout << boost::format("%2% %1%") % 36 % 77; //使用str自由函数: string s2 = str( format("%2% %1%") % 36 % 77 ); 1. 可选的是,在第3步之后,你可以重用一个format对象,并在第2步重新启动:使用相同的格式字符串格式化新变量,从而节省了步骤1中涉及的昂贵处理。 总之,format类将格式化字符串(最终带有类似printf的指令)转换为内部流上的操作,并最终将格式化的结果作为字符串返回,或直接返回到输出流。 ## 输入操作符 ## ### 为什么使用重载操作符 ### format库在形成过程中考虑过很多系统形式的实现,比如完全仿照printf(),在函数里接受所有待格式化参数: format("%s, %d...", x0, x1, ...); 基于类型安全的考虑,format不能使用省略号来实现可变参数。如果使用函数的调用形式,那么就需要定义不同参数类型的模板函数,比如: template<class T1, class T2, .., class TN> string format(string s, const T1& x1, ...., const T1 &xN); 但是C++编译期可能不支持可变参数模板特性,因此,为了保证代码的兼容性就必须使用操作符的方式来接受参数,而且输出/输出流的operator<< 已经应用了很多年,证明了这种方式确实有效。 二元操作符operator%()的声明如下: format& operator%(T& x); 它接受format对象和任意值作为参数,然后返回format对象的引用,因此可以在对返回的format对象实施%操作符,… 从而能接受无限个待格式化的参数 ### 为什么使用operator% ### 为什么不使用operator<<,operator\[\],operator()… 而要使用operator% * operator<<已经被定义为流输出,如果format也使用operator<<,那么在编写代码时很容易造成概念上的混乱,编译期无法区分那些是用于格式化,那些是用于流输出;而且format本身也支持流输出,还有于其他算术运算符的优先级问题。总之,operator<<不是一个好选择 * operator\[\]、(),`,`当然也可以使用,但是代码风格不如%好看,也不能清楚的表明格式化操作意图。注:assign库里重载了()和`,`操作符,同样串联了多个参数: format(...)[x][y][z] format(...)(x)(y)(z) format(...) x, y, z * printf也是使用%来格式化操作的,因此format遵循了前例 ## 高级用法 ## format提供了printf的功能,但它并不等同于printf函数。在通常的格式化字符串以外,format类还有几个高级功能,可以在运行时修改格式化选项、绑定输入参数。 这些高级功能用到的函数有: basic_format& bind_arg(int argN, const T& val); * 把格式化字符串第argN个位置的输入参数固定为val,即使调用clear()也保持不变,除非调用clear\_bind()和clear\_binds() basic_format& clear_bind(int argN); * 取消格式化字符串第argN位置的参数绑定 basic_format& clear_binds(); * 取消格式化字符串所有位置的参数绑定,并调用clear basic_format& modify_item(int itemN, T manipulator) * 设置格式化字符串第itemN位置的格式化选项,manipulator是一个boost::io::group()返回的对象 boost::io::group(T1 a1, ..., Var const & var) * 它是一个模板函数,最多支持10个参数(10个重载形式),可以设置输入/输出流操作器以指定格式或输入参数值,输入/输出流操作器位于头文件`<iomapip>` ## 例子: ## using namespace std; using boost::format; using boost::io::group; * 简单的输出,重新排序 cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style. //It prints : "11 22 333 22 11 \n" * 更精确的格式化,使用Posix-printf位置指令 cout << format("(x,y) = (%1$+5d,%2$+5d) \n") % -23 % 35; // Posix-Printf style // It prints : "(x,y) = ( -23, +35) \n" * 经典的printf指令,没有重排序 cout << format("writing %s, x=%s : %d-th step \n") % "toto" % 40.23 % 50; //It prints : "writing toto, x=40.23 : 50-th step \n" * 表达同一件事的几种方式 cout << format("(x,y) = (%+5d,%+5d) \n") % -23 % 35; cout << format("(x,y) = (%|+5|,%|+5|) \n") % -23 % 35; cout << format("(x,y) = (%1$+5d,%2$+5d) \n") % -23 % 35; cout << format("(x,y) = (%|1$+5|,%|2$+5|) \n") % -23 % 35; // all those print : "(x,y) = ( -23, +35) \n" * 使用操纵符修改格式字符串 format fmter("_%1$+5d_ %1$d \n"); format fmter2("_%1%_ %1% \n"); fmter2.modify_item(1, group(showpos, setw(5)) ); cout << fmter % 101 ; cout << fmter2 % 101 ; // Both print the same : "_ +101_ 101 \n" * 使用带参数的操纵符 cout << format("_%1%_ %1% \n") % group(showpos, setw(5), 101); // 每次出现%1%时都应用操纵符,因此它打印:“_ +101_ +101 \n” * 新的格式特性:‘绝对表格’,有用的内部循环,以确保字段打印在同一位置从一行到下一行,即使前面的参数的宽度可能变化很大。 for(unsigned int i=0; i < names.size(); ++i) cout << format("%1%, %2%, %|40t|%3%\n") % names[i] % surname[i] % tel[i]; * 高级用法 format fmt("%1% %2% %3% %2% %1% \n") ; //声明format对象,有三个输入参数,五个格式化参数 cout << fmt %1 %2 % 3; fmt.bind_arg(2, 10); //将第二个输入参数固定为数字10 cout << fmt %1 %3; //输入其他两个参数 fmt.clear(); //清空缓冲,但绑定的参数不变 //在%操作符中使用group(), 指定输入/输出流操作符第一个参数显式为8进制 cout << fmt % group(showbase, oct, 111) % 333; fmt.clear_binds(); //清楚所有绑定参数 //设置第一个格式化项, 十六进制,宽度为8,右对齐,不足位用*填充 fmt.modify_item(1, group(hex, right, showbase, setw(8), setfill('*'))); cout << fmt % 49 % 20 % 100; ## 性格优化 ## printf不进行类型检查,直接向stdout输出,因此它的速度非常快,而format较printf做了很多安全检查的工作,因此性能略差,速度上要慢一些 如果很在意format的性能,那么可以先建立const format对象,然后拷贝这个对象进行格式化操作,这样比直接使用format对象能够提高速度: const format fmt("%10d %020.8f"); //常量对象 cout << format(fmt) % 62 % 2.236; //拷贝使用 ## 格式化语法 ## boost::format( format-string ) % arg1 % arg2 % ... % argN format-string包含文本,其中的特殊指令将被指定参数格式化后的字符串所取代。C和c++世界中的遗留语法是printf所使用的语法,因此format可以直接使用printf格式字符串,并产生相同的结果(几乎在所有情况下。详情请参阅[printf不兼容][printf]) 这个核心语法得到了扩展,允许了新的特性,同时也适应了c++流上下文。因此,format接受format-strings中几种形式的指令: * 遗留的printf格式字符串,比如 * `%05d`:输出宽度为5的整数,不足位用0补充 * `%-8.4f`:输出左对齐,总宽度为8,小数位3的浮点数 * `% 10s`:输出10位的字符串,不足位用空格补充 * `%05X`:输出宽度为5的大写十六进制整数,不足位用0填充 * %spec其中spec是一个[printf格式规范][printf 1]。spec传递格式化选项,如宽度、对齐方式、用于格式化数字的数字基数,以及其他特定的标志。但是printf的经典类型规范标志在格式上的含义较弱。它仅在内部流和/或格式化参数上设置适当的标志,但不要求相应的参数为特定类型。 * 比如:2$x,对于printf来说意味着“打印参数2,这是一个整数,以十六进制表示”,对于格式来说仅仅意味着“打印参数2的stream basefield标志设置为十六进制”。 * `%| spec|` * 与printf格式选项功能相同,但两边增加了竖线分隔,可以更好的区分格式化选项和普通字符 * 例如:"`%|-5|`"将格式化下一个变量,宽度设置为5,并左对齐,就像下面的printf指令:`"%-5g", "%-5f", "%-5 "`… * `%N%` * 标记第N个参数,相当于占位符,不带任意其他格式化选项 [20210518164736323.png]: /images/20220413/0ce6672d6c2e44d491401d336789ca4a.png [printf]: https://www.boost.org/doc/libs/1_76_0/libs/format/doc/format.html#printf_differences [printf 1]: https://www.boost.org/doc/libs/1_76_0/libs/format/doc/format.html#printf_directives
还没有评论,来说两句吧...