他最大的特色是在于它可以使用C 语言中printf 的格式化字串,来针对C++ 的iostream 做输出、或是产生格式化的字串;相较于C++ iostream 的manipulator,boost::format 在使用上更为直觉、简单。 而且和printf 不同的地方在于,他又有C++ iostream 的type safe、可以支持自定义类型的输出
官方网站的介绍可以参考: http://www.boost.org/doc/libs/1_44_0/libs/format/index.html
一、C printf 与C++ iostream
一般在C语言的时候,大家应该都很习惯用printf这个函式( 参考 )来做输出的动作。 由于printf有着强大、简单的格式化输出的能力,所以很多人就算是使用C++,也会舍弃较为安全的iostream( 参考 ),而继续使用printf、fprintf、sprintf这类的函式来做字串的格式化处理、输出。
不过实际上,C 语言的printf 在使用上并不是非常地安全。 最主要的问题,在于使用printf 的时候,并不是type safe 的! 一个很简单的例子就是:
char * x = " abcd "; printf( " %d\n ", x );
由于在使用printf 输出的时候,需要先指定输出变量的类型(%d、%f 等),所以其实一不小心就有可能弄错,变成像上面一样,指定了错误的输出类型。 另外,也由于printf 设计上的问题,所以如果要输出自定义类型,会变得相对麻烦,而如果是要用sprintf这个函数来产生格式化的字符串的话,更有可能产生记忆体使用上的问题。
基本上C++ 的iostream 就已经解决这些问题了。 如果是使用C++ 的iostream 的话,其实在各方面的问题相对都少很多,用起来也相对简单很多;对于自定义类型的输出,更可以用操作符重载的方法,来为每一个类别都写一个属于自己、并且符合iostream 使用方法的输出。
二、boost::format 基本使用
Boost的Format这个函数库( 官方介绍 ),基本上就是为了让使用者可以更简单地使用C++的iostream来进行格式化输出而开发的! boost::format 提供了一个和C 的printf 类似的格式化字串(format string)的语法定义,来让使用者可以非常简单地做到和printf 一样效果的格式化输出,而同时,他也保有了C++ 的iostraem 的各项优势,对于要做格式化输出的C++ 程式开发人员来说,boost::format 应该是个相当好用、也值得一试的的函数库!
boost::format 是一个header-only 的函数库,只要准备好头文件,不用预先编译就可以使用了,在使用上相当地便利。 而在这个函数库里,主要是提供了一个format的类别(注一) ,来让程式开发者来做操作。 下面是一个简单的例子:
#include <stdlib.h> #include <iostream> #include <boost/format.hpp> using namespace std; int main( ) { cout << boost::format( "%2.3f, %d" ) % 1.23456 % 12 << endl; }
首先,要使用boost::format,我们必须要先include「boost/format.hpp」这个头文件。
而boost::format 最接近printf 的用法,也就是上面这样的形式(POSIX-printf style)了~这样的写法在透过cout 做输出后的结果,会和
printf( "%2.3f, %d\n" , 1.23456, 12 );
完全一样。
而除了上面这种「Posix-Printf style 」以外,也还有所谓的「simple style」(简单风格)的用法可以使用,下面就是一个简单的例子:
cout << boost::format( "%1%, %2%" ) % 1.23456 % 12 << endl;
在这种风格的写法中,是在格式化字串里,用「%1%」来代表之后的第一个变数、用「%2%」来代表第二个变数;透过这样的定义,我们就可以自行调整变数的顺序、同时也可以重复地使用某一项变数了~例如:
cout << boost::format( "%1%, %2%, %1%" ) % 1.23456 % 12 << endl;
这样写的话,输出的结果就会是「1.23456, 12, 1.23456」。 不过由于这个写法没有特别指定格式化的设定,所以所有变数都会用预设的方法做输出。
1. boost::format 对象的操作
前面已经有提到,boost::format 实际上是一个类型,在使用时实际上会产生一个型别是boost::format 的对象,来进行后续的操作;之后所有的变数,都是透过呼叫operator %的方式,依序传给这个物件(注二) ,最后再透过operator<<把他的资料输出传给cout。
相较于此,printf 本身是一个参数数目可变(variable-length argument)的函数,所以所有要输出的变量,都是用逗号隔开、以函数参数的方式传进去的。 所以这两者虽然在程式的写法上看起来很类似,但是在概念和实作方法上,是完全不同的。
像下面这个例子:
cout << boost::format( "%1%, %2%" ) % 1.23456 % 12 << endl;
实际上可以看成:
cout << ( ( boost::format( "%1%, %2%" ) % 1.23456 ) % 12 ) << endl;
而也由于boost::format 实际上是以对象的形式在运作的,所以实际的执行过程,就相当于:
boost::format fmt ( "%1%, %2%" ); fmt % 1.23456; fmt % 12; cout << fmt << endl;
这也代表使用者可以把boost::format这个对象记录下来,重复地使用。
例如下面就是一个重复使用boost::format物件的例子:
boost::format fmt ( "Test:< %1.2f, %1.2f >" );cout << ( fmt % 1.234 % 123.1 ) << endl;cout << fmt % 5.678 % 1 << endl;
不过要注意的是,透过operator%传给boost::format对象( fmt )的变量是会储存在物件内部的,所以可以分批的传入变数;但是如果变量的数量不符合的话,在编译阶段虽然不会出现错误,可是到了执行阶段还是会让程序崩溃,所以在使用上必须小心一点。 不过,在有输出后,是可以再重新传入新的变量、重复使用同一个boost::format 对象的。
2. 透过boost::format 产生字串
前面提的方法,都是把boost::format 产生的结果直接输出到ostream 的用法,那如果是要把格式化输出的结果产生成字符串继续使用呢? 很简单,因为boost 已经有提供对应的函数可以做这件事了~基本上有两种方法,第一个方法是用boost::str() 这个函数:
string tmp = boost::str( boost::format( "<%1%>" ) % "Hi!" );
另一个方法则是用boost::format::str() 这个函式:
boost::format fmt ( "<%1%>" ); fmt % "Hi!" ; string tmp = fmt .str();
或是:
string tmp = ( boost::format( "<%1%>" ) % "Hi!" ).str( );
这两者基本上是一样的,只是写法不同罢了。
3. 语法细节
前面大概提到了所谓的POSIX printf style 和simple style 两种用法。 实际上boost::format所使用的格式化字串的语法,是依照Unix98 Open-group printf ,再做一些延伸而定的;它的形式是:
%[ N $][ flags ][ width ][. precision ] type-char
其中大部分的内容都和传统的printf相同( 参考 ),只有部分不一样。 (注四)
像是在flags 的部分,boost::format 除了本来的「-」是向左对齐外,还多了新的置中对齐的「=」、以及内部对齐(internal alignment)的「_」,这两者就是printf 没有的。 而除了可以用「 %% 」来输出「%」符号外,也多了可以用「 % n t 」来填入n个空格、或是用「 %| n T X | 」来填入n个X (X 为单一字元)的功能。
而在实际使用上,大致应该分成下面几种形式:
% N % :(Simple style)最简单、没有格式化的简化写法,其中N只是单纯标记是第几个变量。
% spec :(POSIX-printf style格式化字串)这部分主要是相容于printf的写法,基本上可以把本来用在printf上的写法直接拿来用。 当然,spec 的部分也有支援boost::format 额外定义的新东西可以用。
%| spec | :这是用「|」来做分隔的表示方法。 spec 基本上和前者是相同的,这种写法主要的优点是可以省略指定型别的字元(printf 里的「type-conversion character」),同时也可以加强代码的可读性。
例如:「 %| -5 | 」就是代表向左对齐、宽度是5,根据变量型别的不同,和「 %-5g 」、「 %-5f 」等是等价的。
其中,看起来比较特别的写法,或许是「 %|1$+4.2| 」这样的形式吧~它代表的意义基本上就是把第一个变数( 1$ ),以「 +4.2 」的形式来做输出;而这边也没有特别指定输出的型别,所以在执行阶段的行为,可能会根据传入的变数的型别而有所不同。
4. 范例
下面的例子说明boost::format简单的工作方式
// 方式一 cout << boost::format("%s") % "输出内容" << endl; // 方式二 std::string s; s = str( boost::format("%s") % "输出内容" ); cout << s << endl; // 方式三 boost::format formater("%s"); formater % "输出内容"; std::string s = formater.str(); cout << s << endl; // 方式四 cout << boost::format("%1%") % boost::io::group(hex, showbase, 40) << endl;
三、效能问题
boost::format虽然在使用上算是满方便的,但是实际上在效能面来说,并不是非常地好,这点在官方的网页就有特别提出来。 基本上,在一般状态下,使用printf 的效能会是最好的,iostream 会比printf 慢一些,而boost::format 则由于又有其他的overhead,所以又会更慢。 官方也有提供一些测试数据,如果在release 模式下,iostream 的操作所需的时间大约会是printf 的1.6 倍;而boost::format 所需的时间则会是iostream 的2 ~ 3 倍左右,也就是大约是printf 的3 ~ 5 倍。
从这个测试数据应该就可以发现,其实boost::format 的效能并不好。 所以如果程式本身的效能瓶颈是在这类的字符串输出、处理的话,那使用boost::format 可能就不是一个好的选择,因为他确实有可能会让效能变差;所以在这种情况下,最好的方法应该还是回去用printf 了~
不过实际上,一般的程式主要的效能瓶颈应该不会是在这一部分,所以在这种状况下使用boost::format 的话,应该不会对整体效能造成很大的影响;相对地,却有可能因为使用boost::format 而减少不少开发时的时间成本。 所以如果是在这种状况下的话,boost::format 应该还是有相当的实用性的。
四、结语
对于boost::format 的介绍,大概就到这先告一个段落了。 其实,讲的应该不算是很完整,有不少细节都被Heresy 跳过了,只是一个简单地介绍罢了。 真正要完全学会的话,可能还是得回官方网站看看了;相信如果愿意花时间的话,应该可以挖到更多进阶的用法才对!
附注
实作上是一个template 的class:basic_format。
boost::format的operator%定义方法其实和ostream的operator<<很类似,它的形式是「 format& format:: operator %( const T& x) 」,会回传一个format的参考,所以可以一直用operator % 串下去。
对于使用者自订的型别,只要有定义operator<<,让他可以透过iostream 输出,就可以用在boost::foramt 上。
Visual C++的printf似乎不支援格式化字串的「 N $ 」(positional format specification),不过在gcc上应该是可以用的。