Boost Format Library
<boost/format.hpp>中的format类提供一种类似于printf的格式化,这种格式化是类型安全的,并且允许输出用户自定义类型。
* Synopsis
* How it works
* Examples
* Syntax
o printf format-specification syntax
o Incompatibilities with printf
* Manipulators and the internal stream state
* User-defined types
* Alternatives
* Exceptions
* Performance
* Class Interface Extract
* Rationale
大纲
一个format对象从一个format-string中被构造出来, 然后通过反复的调用操作符%来输入参数。随后这些参数都会被转换为字符串,并最终根据format-string合并为一个字符串。
它是怎样工作的
1.当你调用format(s)时,(s是指format-string),它创建一个对象,这个对象会解析format-string,查找出format-string里面所有指示符,并为下一步准备内部结构。
2.然后,或者立即将变量代入格式程序中,如
或者在稍后将变量代入格式程序中,如
这些变量会被抛入到内部流中,如果format-string中给出了格式化选项,那么内部流根据选项来设置状态,format对象会存储最终的字符串结果。
3.一旦所有参数都被代入,你就可以将format对象传给一个流,或者通过成员函数str()或boost名字空间中的自由函数str(const format&)得到字符串。作为结果的字符串将会一直保留在format对象中,直到其他参数被传入,此时对象将会被重新初始化。
4. 可以考虑,进行第3步以后,你可以重复使用一个format对象,并接着从第二步开发 : fmter % 18 % 39;
为了能够格式化新变量,保存在第一步中执行的代价不菲的操作。
总而言之, format类将一个format-string解释成对内部流的多个操作,并最终以字符串的格式返回格式化结果,或直接输入到输出流中。
例子:
* 通过重新排序进行简单的输出 :
它输出 : "11 22 333 22 11 /n"
* 通过后缀指令,得到更精确的格式 :
它输出 : "(x,y) = ( -23, +35) /n"
*经典的输出指令,不重新排序 :
它输出 : "writing toto, x=40.23 : 50-th step /n"
* 表达同一种输出的多种方式 :
所有语句都输出: "(x,y) = ( -23, +35) /n"
* 通过操纵函数修改format-string :
输出是一样的 : "_ +101_ 101 /n"
* 使用带有参数的操纵函数 :
操纵函数会被应用到所有包含%1%的地方,因此会输出 : "_ +101_ +101 /n"
*新格式化特色:‘absolute tablations',使用内部循环来保证一个字段在多行中都是被打印在同一个位置,即使前一个参数的宽度变化很大。
names, surnames和tel使用某些std::vector时 它输出:
Marc-Fran?ois Michel, Durand, +33 (0) 123 456 789
Jean, de Lattre de Tassigny, +33 (0) 987 654 321
语法
boost::format( format-string ) % arg1 % arg2 % ... % argN
format-string包含文本,文本中的指示符将会被由给出的参数转换而来的字符串替代。
合法的语法是C和C++中被printf使用的语法,因此,fromat可以直接使用printf的format-string,并产生同样的结果
核心的语法被拓展,从而可以产生一些新特色,并与C++的streams搭配使用。因此。format在format-string中可以接受多种指示符。
*合法的printf格式化字符串:%spec,spec是一个printf格式化说明符。
spec传递格式化选项,例如宽度,位置,格式化数据时的数据类型说明。但是printf中的经典类型说明符在format中不够充分。它仅仅在内部stream中设置合适的标志以及格式化参数,却不能表明将对应的参数限定为一个特定的类型。
e.g:格式2$x,在printf中是指”用十六进制将整数参数2打印“,在format中仅仅是指“将stream的标志设置为16进制,并将参数2输出”。
*%|spec|,spec是一个printf格式化说明符
括号被用来改善format-string的可读性,同时也使得spec中的类型转换字符成为多选的。这个信息对于C++变量是不需要的,但是对于直接的printf语法,由于它通常提供了一种类型转换字符,判断出格式说明符的结尾,因此这些信息是很必要的。
e.g. : "%|-5|" 将下一个参数的宽度设置为5, 左对齐方式,就像下列printf指示符 : "%-5g", "%-5f", "%-5s" ..
* %N%
这个简单的定位符需要第n个参数来进行格式化,不需要任何格式化选项。
(它只是printf定位指令(如“%N$s“)的一种简写,主要优点是可读性强,不需要使用类型转换符。)
新特性在标准的printf格式说明符基础上进行实现,如居中。可以参考新格式的说明。
-printf 格式说明符
boost.format严格依照Unix98 Open-group printf 语法来支持printf格式说明符, 不依照标准C的printf语法是由于它不支持定位参数。(对于普通标记来说,它们的意义是一样的,所以这不会成为一件令人头痛的事情)
注意,在同一个format string中混合使用定位格式符(如%3$+d)和非定位的格式符(如%+d)将会产生一个错误。
在Open-group说明中,多次重复使用一个参数(如%1$d%1$d)将会导致不确定行为,boost.format却支持这种使用方式。唯一的限制是如果在format string中出现的最大数字是P,那么就需要P个参数。 (如在 "%1$d %10$d",中 P == 10 ).参数的个数过多或多少都会出现一个异常。
一个说明符的格式为 : [ N$ ] [ flags ] [ width ] [ . precision ] type-char
在方括号中的字段是可选的。各字段的意义如下 :
* N $ 说明格式说明符将应用与第N个参数。(它被称为定位说明符)
如果没有出现定位说明符,那么参数就会按顺序一一对应。(在后面提供定位说明符将会出现错误)
* flags 是下列符号的任意一种排列 :
Flag 意义 对内部流的作用
'-' 左对齐 N/A ((随后应用于字符串上)
'=' 中间对齐 N/A (随后应用于字符串上)
- note : 新特性
'_' 内部对齐
- note : 新特性
'+' 显示符号 正号也显示
'#' 显示基数和小数点 显示基数和小数点
'0' 0填充
' ' 如果字符串不含正负号,
用空格填充符号 N/A (随后应用于字符串上)
* width 指定转换后的字符串最小宽度。如果必要的话, 字符串会被复制填充,或格式字符(如 flags '0', '-', ..)
注意,宽度不只是应用于转换流中。为了支持用户自定义类型的输出,宽度是在对整个参数对象进行转换后进行处理的。
* precision (在点号后面), 设置流的精度
o 当输出浮点数时, 它设置数字的位数
+ 科学计数法时,指小数点后面的位数
+ 普通模式下,指所有数字的位数
o 与字符有关类型一起使用时, 它指的是另外一个意义 : 转换字符串被截断为从开始起的长度为precision的字符串。(宽度填充是在精度设置以后完成的.)
* type-char 它不是强制关联参数的类型,而只是说明某些与类型有关的设置。
Type-Char 意义 对流起的作用
p or x 十六进制 十六进制输出
o 八进制输出 设置八进制输出
e 浮点数科学计数法 设置浮点数科学计数法
f 浮点数定长显示 设置浮点数定长表示
g 浮点数默认的普通表示 回复默认表示
X, E or G 和他们对应的小写字母意义相同,
d, i or u 十进制输出 十进制输出
s or S 字符串输出
c or C 单字符输出 只使用转换字符串的首字符
% 输出字符%
新增的格式说明
*添加居中对齐和内部对齐
* %{nt} , n是一个正整数, 插入n个t制表符。它表示,如果字符串不足n个字符的话,填充字符到n个字符。
* %{nTX} 同样方式插入制表符, 但将X作为默认制表符
与printf的对比
假设你有连个变量 x1, x2 (printf支持的内置类型),和一个在printf中使用的格式字符串 s : printf(s, x1, x2);
在大多数情况下,它和 cout << format(s) % x1 % x2;的输出是一样的。但是,由于有极少数格式说明符不容易被引进到boost.format中所以使用boost.format模拟printf输出是存在一些缺陷。在很多情况下,这些不支持的选项是可以忽略,所以printf的格式字符串一直使用于boost.format中并产生相同的结果。
下面就列举了所有这些不同的地方 :
* '0' and ' ' options : printf 对于非数值转换,printf忽视此选项,但是format将他们应用于所有类型。
* 对printf而言,精度和整数参数一起使用有特殊的含义 :
printf( "(%5.3d)" , 7 ) ; prints ? ( 007) ?
而format此时忽略精度选项
* printf '选项在format中不起作用。
* 对星号使用宽度或精度,此选项在printf中用来读取参数的字段(现在已不支持)。
另外, 注意到特殊的 'n' 类型说明符 (用来告诉printf,将输出的字符数存储在一个变量中)在format中不起作用。因此他们输出的结果还是相同的。要获取boost.format输出的字符数,可以使用size()成员函数:
用户自定义类型的输出
所有对stream的状态进行修改的标志将递归作用于用户自定义类型中。如:对于一个Rational类,我们可能写如下代码:
对于其他选项又有所不同。举个例子说,设置的宽度选项将会作用于对象最终的输出,而不是在内部内部起作用,很幸运是这样的。
但是0 和' '选项也是同样的 ( '+'直接设置流的状态为显示符号. 与'+'不同,流中没有与0和' '对应的状态符号),它的输出看起来就不是很自然:
可以通过设计Rational的<<操作符来处理流的宽度,对齐方式等参数以得到更好的输出结果。
操纵器,流的内部状态
format内部使用的流的状态在输出一个参数前被保存起来,在输出后又被恢复。所以对状态的修改只对一个参数起作用。流默认使用的是标准状态:精度6,宽度0,右对齐,十进制表示。
format内部流的状态可以被和参数一起输入的操纵器修改; 通过使用group函数, 如 :
当在一个group中传入N项时, Boost.format需要将操纵器和参数区分处理,因此使用group 应该遵守下列法则 :
1. 被输出的对象应该作为group中的最后一项。
2. 前面N-1项被当作manipulators, 他们如果产生输出,输出不会被处理。
每次处理后面的参数时,这些manipulators会传给stream. 在format-string内部的格式选项会被manipulators改写. 例如,在下列代码中,, 十六进制manipulator 比 format-string中的d 类型说明符(十进制格式符)的优先级更高:
异常
Boost.format 在格式化对象的使用时,执行一系列的规则. format-string必须遵循上述语法, 在输出最终字符串是,用户提供的参数数目必须正确。
当format检查到上述规则没有被满足是,它会抛出一个异常,如果进行不处理,就会产生错误。
但是用户可以根据自己的需要进行修改,并根据错误类型使用下列函数抛出异常 :
unsigned char exceptions(unsigned char newexcept); // query and set
unsigned char exceptions() const; // just query
用户可以通过合并下列选项来设置参数newexcept
* boost::io::bad_format_string_bit 只抛出format-string不正确格式错误。
* boost::io::too_few_args_bit 只抛出所有参数都被传入前就查询结果字符串的错误。
* boost::io::too_many_args_bit 只抛出传入参数过多错误。
* boost::io::out_of_range_bit 只抛出下标越界错误
* boost::io::all_error_bits 可以用来抛出所有错误
* boost::io::no_error_bits 不抛出任何错误.
举个例子,如果你不希望boost.format检查到参数个数不对,你可以定义一个函数,在这个函数内创建format对象,并设置format对象的异常设置。
这样就允许用户输入过多参数 (他们将会被简单地忽略掉) :
如果我们在提供所有参数之前查询结果,对应部分将不会显示。
// 显示 " __ _1_ /n"
性能事项
在格式化排序输出一些内置类型参数时,可以将boost::format和printf的执行进行比较,也可以和在流上的手工操作进行比较,从而估计出boost::format的性能。比较结果可能与编译器、标准库的实现、选择的format-string以及参数有关。
由于stream普通的执行底层都是调用printf系列函数来进行实际格式化,一般而言,printf将会明显比stream的直接操作要快。由于重新排序产生的消耗,stream的直接操作要比boost::format快(boost::fromat的花费的时间是stream的2-5倍)。
当迭代格式化成为性能的瓶颈时,通过解析format-string并存储在format对象内部,并在使用时复制给其他format对象,性能可以轻微地提高,就像以下所示。
const boost::format fmter(fstring);
dest << boost::format(fmter) % arg1 % arg2 % arg3 ;
为了检测出性能对比结果,作者通过4种方法来测量迭代格式化的执行时间。
1. printf
2. stream的手工输出
3. boost::format 用以上复制常量的方式
4. 直接使用 boost::format
实验使用g++-3.3.3编译器,以下是测量结果 (单位:秒,并提供出比值) :
string fstring="%3$0#6x %1$20.10E %2$g %3$0+5d /n";
double arg1=45.23;
double arg2=12.34;
int arg3=23;
- release模式:
printf : 2.13
nullStream : 3.43, = 1.61033 * printf
boost::format copied : 6.77, = 3.1784 * printf , = 1.97376 * nullStream
boost::format straight :10.67, = 5.00939 * printf , = 3.11079 * nullStream
- debug 模式 :
printf : 2.12
nullStream : 3.69, = 1.74057 * printf
boost::format copied :10.02, = 4.72642 * printf , = 2.71545 * nullStream
boost::format straight :17.03, = 8.03302 * printf , = 4.61518 * nullStream
类接口
基本原理
format类的宗旨是为了提供一个更佳的、C++方式的、类型安全的、类型可扩展的、使用streams的printf等价物。
* 支持定位参数 (需要国际化支持)。
* 不限制参数的个数。
* 使格式化命令看起来更直观。
* 支持利用控制器来修改参数的显示方式。 这是对format-string 语法的一种补充。
* 可以接受任何类型的变量, 如何转换为字符串将取决于stream. 这一点关系到用户自定义类型。
* 在printf兼容的基础上, 支持类型安全和类型可扩展特性.
在设计过程中,出现过很多事情,并且做出了很多决定,这些决定在直观上可能并不正确,但是他们都是处于多种原因的考虑而被选择的。
<boost/format.hpp>中的format类提供一种类似于printf的格式化,这种格式化是类型安全的,并且允许输出用户自定义类型。
* Synopsis
* How it works
* Examples
* Syntax
o printf format-specification syntax
o Incompatibilities with printf
* Manipulators and the internal stream state
* User-defined types
* Alternatives
* Exceptions
* Performance
* Class Interface Extract
* Rationale
大纲
一个format对象从一个format-string中被构造出来, 然后通过反复的调用操作符%来输入参数。随后这些参数都会被转换为字符串,并最终根据format-string合并为一个字符串。
cout
<<
boost::format(
"
writing %1%, x=%2% : %3%-th try
"
)
%
"
toto
"
%
40.23
%
50
;
// 输出 "writing toto, x=40.230 : 50-th try"
// 输出 "writing toto, x=40.230 : 50-th try"
它是怎样工作的
1.当你调用format(s)时,(s是指format-string),它创建一个对象,这个对象会解析format-string,查找出format-string里面所有指示符,并为下一步准备内部结构。
2.然后,或者立即将变量代入格式程序中,如
cout
<<
format(
"
%2% %1%
"
)
%
36
%
77
);
或者在稍后将变量代入格式程序中,如
format fmter(
"
%2% %1%
"
);
fmter % 36 ; fmter % 77 ;
fmter % 36 ; fmter % 77 ;
这些变量会被抛入到内部流中,如果format-string中给出了格式化选项,那么内部流根据选项来设置状态,format对象会存储最终的字符串结果。
3.一旦所有参数都被代入,你就可以将format对象传给一个流,或者通过成员函数str()或boost名字空间中的自由函数str(const format&)得到字符串。作为结果的字符串将会一直保留在format对象中,直到其他参数被传入,此时对象将会被重新初始化。
//
fmter 已经被创建并传入参数,它可以输出结果 :
cout << fmter ;
// 你可以从中取出作为结果的字符串 :
string s = fmter.str();
// 可以获取多次 :
s = fmter.str( );
// 你也可以一次性执行所有操作 :
cout << boost::format( " %2% %1% " ) % 36 % 77 ;
// 使用只有函数str:
string s2 = str( format( " %2% %1% " ) % 36 % 77 );
cout << fmter ;
// 你可以从中取出作为结果的字符串 :
string s = fmter.str();
// 可以获取多次 :
s = fmter.str( );
// 你也可以一次性执行所有操作 :
cout << boost::format( " %2% %1% " ) % 36 % 77 ;
// 使用只有函数str:
string s2 = str( format( " %2% %1% " ) % 36 % 77 );
4. 可以考虑,进行第3步以后,你可以重复使用一个format对象,并接着从第二步开发 : fmter % 18 % 39;
为了能够格式化新变量,保存在第一步中执行的代价不菲的操作。
总而言之, format类将一个format-string解释成对内部流的多个操作,并最终以字符串的格式返回格式化结果,或直接输入到输出流中。
例子:
using
namespace
std;
using boost::format;
using boost::io::group;
using boost::format;
using boost::io::group;
* 通过重新排序进行简单的输出 :
cout
<<
format(
"
%1% %2% %3% %2% %1%
"
)
%
"
11
"
%
"
22
"
%
"
333
"
;
//
简单方式.
它输出 : "11 22 333 22 11 /n"
* 通过后缀指令,得到更精确的格式 :
cout
<<
format(
"
(x,y) = (%1$+5d,%2$+5d)
"
)
%
-
23
%
35
;
它输出 : "(x,y) = ( -23, +35) /n"
*经典的输出指令,不重新排序 :
cout
<<
format(
"
writing %s, x=%s : %d-th step
"
)
%
"
toto
"
%
40.23
%
50
;
它输出 : "writing toto, x=40.23 : 50-th step /n"
* 表达同一种输出的多种方式 :
cout
<<
format(
"
(x,y) = (%+5d,%+5d)
"
)
%
-
23
%
35
;
cout << format( " (x,y) = (%|+5|,%|+5|) " ) % - 23 % 35 ;
cout << format( " (x,y) = (%1$+5d,%2$+5d) " ) % - 23 % 35 ;
cout << format( " (x,y) = (%|1$+5|,%|2$+5|) " ) % - 23 % 35 ;
cout << format( " (x,y) = (%|+5|,%|+5|) " ) % - 23 % 35 ;
cout << format( " (x,y) = (%1$+5d,%2$+5d) " ) % - 23 % 35 ;
cout << format( " (x,y) = (%|1$+5|,%|2$+5|) " ) % - 23 % 35 ;
所有语句都输出: "(x,y) = ( -23, +35) /n"
* 通过操纵函数修改format-string :
format fmter(
"
_%1$+5d_ %1$d
"
);
format fmter2( " _%1%_ %1% " );
fmter2.modify_item( 1 , group(showpos, setw( 5 )) );
cout << fmter % 101 ;
cout << fmter2 % 101 ;
format fmter2( " _%1%_ %1% " );
fmter2.modify_item( 1 , group(showpos, setw( 5 )) );
cout << fmter % 101 ;
cout << fmter2 % 101 ;
输出是一样的 : "_ +101_ 101 /n"
* 使用带有参数的操纵函数 :
cout
<<
format(
"
_%1%_ %1%
"
)
%
group(showpos, setw(
5
),
101
);
操纵函数会被应用到所有包含%1%的地方,因此会输出 : "_ +101_ +101 /n"
*新格式化特色:‘absolute tablations',使用内部循环来保证一个字段在多行中都是被打印在同一个位置,即使前一个参数的宽度变化很大。
for
(unsigned
int
i
=
0
; i
<
names.size();
++
i)
cout << format( " %1%, %2%, %|40t|%3% " ) % names[i] % surname[i] % tel[i];
cout << format( " %1%, %2%, %|40t|%3% " ) % names[i] % surname[i] % tel[i];
names, surnames和tel使用某些std::vector时 它输出:
Marc-Fran?ois Michel, Durand, +33 (0) 123 456 789
Jean, de Lattre de Tassigny, +33 (0) 987 654 321
语法
boost::format( format-string ) % arg1 % arg2 % ... % argN
format-string包含文本,文本中的指示符将会被由给出的参数转换而来的字符串替代。
合法的语法是C和C++中被printf使用的语法,因此,fromat可以直接使用printf的format-string,并产生同样的结果
核心的语法被拓展,从而可以产生一些新特色,并与C++的streams搭配使用。因此。format在format-string中可以接受多种指示符。
*合法的printf格式化字符串:%spec,spec是一个printf格式化说明符。
spec传递格式化选项,例如宽度,位置,格式化数据时的数据类型说明。但是printf中的经典类型说明符在format中不够充分。它仅仅在内部stream中设置合适的标志以及格式化参数,却不能表明将对应的参数限定为一个特定的类型。
e.g:格式2$x,在printf中是指”用十六进制将整数参数2打印“,在format中仅仅是指“将stream的标志设置为16进制,并将参数2输出”。
*%|spec|,spec是一个printf格式化说明符
括号被用来改善format-string的可读性,同时也使得spec中的类型转换字符成为多选的。这个信息对于C++变量是不需要的,但是对于直接的printf语法,由于它通常提供了一种类型转换字符,判断出格式说明符的结尾,因此这些信息是很必要的。
e.g. : "%|-5|" 将下一个参数的宽度设置为5, 左对齐方式,就像下列printf指示符 : "%-5g", "%-5f", "%-5s" ..
* %N%
这个简单的定位符需要第n个参数来进行格式化,不需要任何格式化选项。
(它只是printf定位指令(如“%N$s“)的一种简写,主要优点是可读性强,不需要使用类型转换符。)
新特性在标准的printf格式说明符基础上进行实现,如居中。可以参考新格式的说明。
-printf 格式说明符
boost.format严格依照Unix98 Open-group printf 语法来支持printf格式说明符, 不依照标准C的printf语法是由于它不支持定位参数。(对于普通标记来说,它们的意义是一样的,所以这不会成为一件令人头痛的事情)
注意,在同一个format string中混合使用定位格式符(如%3$+d)和非定位的格式符(如%+d)将会产生一个错误。
在Open-group说明中,多次重复使用一个参数(如%1$d%1$d)将会导致不确定行为,boost.format却支持这种使用方式。唯一的限制是如果在format string中出现的最大数字是P,那么就需要P个参数。 (如在 "%1$d %10$d",中 P == 10 ).参数的个数过多或多少都会出现一个异常。
一个说明符的格式为 : [ N$ ] [ flags ] [ width ] [ . precision ] type-char
在方括号中的字段是可选的。各字段的意义如下 :
* N $ 说明格式说明符将应用与第N个参数。(它被称为定位说明符)
如果没有出现定位说明符,那么参数就会按顺序一一对应。(在后面提供定位说明符将会出现错误)
* flags 是下列符号的任意一种排列 :
Flag 意义 对内部流的作用
'-' 左对齐 N/A ((随后应用于字符串上)
'=' 中间对齐 N/A (随后应用于字符串上)
- note : 新特性
'_' 内部对齐
- note : 新特性
'+' 显示符号 正号也显示
'#' 显示基数和小数点 显示基数和小数点
'0' 0填充
' ' 如果字符串不含正负号,
用空格填充符号 N/A (随后应用于字符串上)
* width 指定转换后的字符串最小宽度。如果必要的话, 字符串会被复制填充,或格式字符(如 flags '0', '-', ..)
注意,宽度不只是应用于转换流中。为了支持用户自定义类型的输出,宽度是在对整个参数对象进行转换后进行处理的。
* precision (在点号后面), 设置流的精度
o 当输出浮点数时, 它设置数字的位数
+ 科学计数法时,指小数点后面的位数
+ 普通模式下,指所有数字的位数
o 与字符有关类型一起使用时, 它指的是另外一个意义 : 转换字符串被截断为从开始起的长度为precision的字符串。(宽度填充是在精度设置以后完成的.)
* type-char 它不是强制关联参数的类型,而只是说明某些与类型有关的设置。
Type-Char 意义 对流起的作用
p or x 十六进制 十六进制输出
o 八进制输出 设置八进制输出
e 浮点数科学计数法 设置浮点数科学计数法
f 浮点数定长显示 设置浮点数定长表示
g 浮点数默认的普通表示 回复默认表示
X, E or G 和他们对应的小写字母意义相同,
d, i or u 十进制输出 十进制输出
s or S 字符串输出
c or C 单字符输出 只使用转换字符串的首字符
% 输出字符%
新增的格式说明
*添加居中对齐和内部对齐
* %{nt} , n是一个正整数, 插入n个t制表符。它表示,如果字符串不足n个字符的话,填充字符到n个字符。
* %{nTX} 同样方式插入制表符, 但将X作为默认制表符
与printf的对比
假设你有连个变量 x1, x2 (printf支持的内置类型),和一个在printf中使用的格式字符串 s : printf(s, x1, x2);
在大多数情况下,它和 cout << format(s) % x1 % x2;的输出是一样的。但是,由于有极少数格式说明符不容易被引进到boost.format中所以使用boost.format模拟printf输出是存在一些缺陷。在很多情况下,这些不支持的选项是可以忽略,所以printf的格式字符串一直使用于boost.format中并产生相同的结果。
下面就列举了所有这些不同的地方 :
* '0' and ' ' options : printf 对于非数值转换,printf忽视此选项,但是format将他们应用于所有类型。
* 对printf而言,精度和整数参数一起使用有特殊的含义 :
printf( "(%5.3d)" , 7 ) ; prints ? ( 007) ?
而format此时忽略精度选项
* printf '选项在format中不起作用。
* 对星号使用宽度或精度,此选项在printf中用来读取参数的字段(现在已不支持)。
另外, 注意到特殊的 'n' 类型说明符 (用来告诉printf,将输出的字符数存储在一个变量中)在format中不起作用。因此他们输出的结果还是相同的。要获取boost.format输出的字符数,可以使用size()成员函数:
format formatter(
"
%+5d
"
);
cout << formatter % x;
unsigned int n = formatter.size();
cout << formatter % x;
unsigned int n = formatter.size();
用户自定义类型的输出
所有对stream的状态进行修改的标志将递归作用于用户自定义类型中。如:对于一个Rational类,我们可能写如下代码:
Rational ratio(
16
,
9
);
cerr << format( " %#x " ) % ratio; // -> "0x10/0x9 "
cerr << format( " %#x " ) % ratio; // -> "0x10/0x9 "
对于其他选项又有所不同。举个例子说,设置的宽度选项将会作用于对象最终的输出,而不是在内部内部起作用,很幸运是这样的。
cerr
<<
format(
"
%-8d
"
)
%
ratio;
//
-> "16/9 " and not "16 /9 "
cerr << format( " %=8d " ) % ratio; // -> " 16/9 " and not " 16 / 9 "
cerr << format( " %=8d " ) % ratio; // -> " 16/9 " and not " 16 / 9 "
但是0 和' '选项也是同样的 ( '+'直接设置流的状态为显示符号. 与'+'不同,流中没有与0和' '对应的状态符号),它的输出看起来就不是很自然:
cerr
<<
format(
"
%+08d
"
)
%
ratio;
//
-> "+00016/9"
cerr << format( " % 08d " ) % ratio; // -> "000 16/9"
cerr << format( " % 08d " ) % ratio; // -> "000 16/9"
可以通过设计Rational的<<操作符来处理流的宽度,对齐方式等参数以得到更好的输出结果。
操纵器,流的内部状态
format内部使用的流的状态在输出一个参数前被保存起来,在输出后又被恢复。所以对状态的修改只对一个参数起作用。流默认使用的是标准状态:精度6,宽度0,右对齐,十进制表示。
format内部流的状态可以被和参数一起输入的操纵器修改; 通过使用group函数, 如 :
cout
<<
format(
"
%1% %2% %1%
"
)
%
group(hex, showbase,
40
)
%
50
;
//
输出 "0x28 50 0x28 "
当在一个group中传入N项时, Boost.format需要将操纵器和参数区分处理,因此使用group 应该遵守下列法则 :
1. 被输出的对象应该作为group中的最后一项。
2. 前面N-1项被当作manipulators, 他们如果产生输出,输出不会被处理。
每次处理后面的参数时,这些manipulators会传给stream. 在format-string内部的格式选项会被manipulators改写. 例如,在下列代码中,, 十六进制manipulator 比 format-string中的d 类型说明符(十进制格式符)的优先级更高:
cout
<<
format(
"
%1$d %2% %1%
"
)
%
group(hex, showbase,
40
)
%
50
;
// prints "0x28 50 0x28 "
// prints "0x28 50 0x28 "
异常
Boost.format 在格式化对象的使用时,执行一系列的规则. format-string必须遵循上述语法, 在输出最终字符串是,用户提供的参数数目必须正确。
当format检查到上述规则没有被满足是,它会抛出一个异常,如果进行不处理,就会产生错误。
但是用户可以根据自己的需要进行修改,并根据错误类型使用下列函数抛出异常 :
unsigned char exceptions(unsigned char newexcept); // query and set
unsigned char exceptions() const; // just query
用户可以通过合并下列选项来设置参数newexcept
* boost::io::bad_format_string_bit 只抛出format-string不正确格式错误。
* boost::io::too_few_args_bit 只抛出所有参数都被传入前就查询结果字符串的错误。
* boost::io::too_many_args_bit 只抛出传入参数过多错误。
* boost::io::out_of_range_bit 只抛出下标越界错误
* boost::io::all_error_bits 可以用来抛出所有错误
* boost::io::no_error_bits 不抛出任何错误.
举个例子,如果你不希望boost.format检查到参数个数不对,你可以定义一个函数,在这个函数内创建format对象,并设置format对象的异常设置。
boost::format my_fmt(
const
std::
string
&
f_string)
{
using namespace boost::io;
format fmter(f_string);
fmter.exceptions( all_error_bits ^ ( too_many_args_bit | too_few_args_bit ) );
return fmter;
}
using namespace boost::io;
format fmter(f_string);
fmter.exceptions( all_error_bits ^ ( too_many_args_bit | too_few_args_bit ) );
return fmter;
}
这样就允许用户输入过多参数 (他们将会被简单地忽略掉) :
cout
<<
my_fmt(
"
%1% %2%
"
)
%
1
%
2
%
3
%
4
%
5
;
如果我们在提供所有参数之前查询结果,对应部分将不会显示。
cout
<<
my_fmt(
"
_%2%_ _%1%_
"
)
%
1
;
// 显示 " __ _1_ /n"
性能事项
在格式化排序输出一些内置类型参数时,可以将boost::format和printf的执行进行比较,也可以和在流上的手工操作进行比较,从而估计出boost::format的性能。比较结果可能与编译器、标准库的实现、选择的format-string以及参数有关。
由于stream普通的执行底层都是调用printf系列函数来进行实际格式化,一般而言,printf将会明显比stream的直接操作要快。由于重新排序产生的消耗,stream的直接操作要比boost::format快(boost::fromat的花费的时间是stream的2-5倍)。
当迭代格式化成为性能的瓶颈时,通过解析format-string并存储在format对象内部,并在使用时复制给其他format对象,性能可以轻微地提高,就像以下所示。
const boost::format fmter(fstring);
dest << boost::format(fmter) % arg1 % arg2 % arg3 ;
为了检测出性能对比结果,作者通过4种方法来测量迭代格式化的执行时间。
1. printf
2. stream的手工输出
3. boost::format 用以上复制常量的方式
4. 直接使用 boost::format
实验使用g++-3.3.3编译器,以下是测量结果 (单位:秒,并提供出比值) :
string fstring="%3$0#6x %1$20.10E %2$g %3$0+5d /n";
double arg1=45.23;
double arg2=12.34;
int arg3=23;
- release模式:
printf : 2.13
nullStream : 3.43, = 1.61033 * printf
boost::format copied : 6.77, = 3.1784 * printf , = 1.97376 * nullStream
boost::format straight :10.67, = 5.00939 * printf , = 3.11079 * nullStream
- debug 模式 :
printf : 2.12
nullStream : 3.69, = 1.74057 * printf
boost::format copied :10.02, = 4.72642 * printf , = 2.71545 * nullStream
boost::format straight :17.03, = 8.03302 * printf , = 4.61518 * nullStream
类接口
namespace
boost
{
template<class charT, class Traits=std::char_traits<charT> >
class basic_format
{
public:
typedef std::basic_string<charT, Traits> string_t;
typedef typename string_t::size_type size_type;
basic_format(const charT* str);
basic_format(const charT* str, const std::locale & loc);
basic_format(const string_t& s);
basic_format(const string_t& s, const std::locale & loc);
basic_format& operator= (const basic_format& x);
void clear(); // reset buffers
basic_format& parse(const string_t&); // clears and parse a new format string
string_t str() const;
size_type size() const;
// pass arguments through those operators :
template<class T> basic_format& operator%(T& x);
template<class T> basic_format& operator%(const T& x);
// dump buffers to ostream :
friend std::basic_ostream<charT, Traits>&
operator<< <> ( std::basic_ostream<charT, Traits>& , basic_format& );
// Choosing which errors will throw exceptions :
unsigned char exceptions() const;
unsigned char exceptions(unsigned char newexcept);
// ............ this is just an extract .......
}; // basic_format
typedef basic_format<char > format;
typedef basic_format<wchar_t > wformat;
// free function for ease of use :
template<class charT, class Traits>
std::basic_string<charT,Traits> str(const basic_format<charT,Traits>& f) {
return f.str();
}
} // namespace boost
template<class charT, class Traits=std::char_traits<charT> >
class basic_format
{
public:
typedef std::basic_string<charT, Traits> string_t;
typedef typename string_t::size_type size_type;
basic_format(const charT* str);
basic_format(const charT* str, const std::locale & loc);
basic_format(const string_t& s);
basic_format(const string_t& s, const std::locale & loc);
basic_format& operator= (const basic_format& x);
void clear(); // reset buffers
basic_format& parse(const string_t&); // clears and parse a new format string
string_t str() const;
size_type size() const;
// pass arguments through those operators :
template<class T> basic_format& operator%(T& x);
template<class T> basic_format& operator%(const T& x);
// dump buffers to ostream :
friend std::basic_ostream<charT, Traits>&
operator<< <> ( std::basic_ostream<charT, Traits>& , basic_format& );
// Choosing which errors will throw exceptions :
unsigned char exceptions() const;
unsigned char exceptions(unsigned char newexcept);
// ............ this is just an extract .......
}; // basic_format
typedef basic_format<char > format;
typedef basic_format<wchar_t > wformat;
// free function for ease of use :
template<class charT, class Traits>
std::basic_string<charT,Traits> str(const basic_format<charT,Traits>& f) {
return f.str();
}
} // namespace boost
基本原理
format类的宗旨是为了提供一个更佳的、C++方式的、类型安全的、类型可扩展的、使用streams的printf等价物。
* 支持定位参数 (需要国际化支持)。
* 不限制参数的个数。
* 使格式化命令看起来更直观。
* 支持利用控制器来修改参数的显示方式。 这是对format-string 语法的一种补充。
* 可以接受任何类型的变量, 如何转换为字符串将取决于stream. 这一点关系到用户自定义类型。
* 在printf兼容的基础上, 支持类型安全和类型可扩展特性.
在设计过程中,出现过很多事情,并且做出了很多决定,这些决定在直观上可能并不正确,但是他们都是处于多种原因的考虑而被选择的。