Boost Format 库
format库提供了一个把参数格式化到一个字符串格式的类,就像printf所做的,但是有两个主要的不同:
format将参数发送给合适的stream,所以它是完全类型安全的并且自然地支持所有的用户自定义的类型。
在format强类型转换中省略号不能被正确使用,需要不确定参数的函数被连续调用操作符%来替代。
下面提供详细信息:
- Documentation文档
(HTML). - 头文件
- format.hpp
: 面向用户的 - format_fwd.hpp
: 用户前向声明的 - format_class.hpp
: 类接口 - format_implementation.hpp:
成员实现 - feed_args.hpp
: 参数传送帮助函数 - free_funcs.hpp
: 自由函数定义 - parsing.hpp
: 提供解析格式化字符串的代码 - group.hpp
: 用于组参数和操纵子的辅助结构 - exceptions.hpp
: 这个库用到的异常 - internals.hpp
: 流格式状态和格式化项的辅助结构
- format.hpp
- 示例程序
- sample_formats.cpp
程序示范了format的简单使用 - sample_new_features.cpp
举例说明了新的格式化特点,它们是被printf语法支持的。比如简单的位置指示,中间对齐和做表。 - sample_advanced.cpp
示范了format的高级特性,比如format对象的重用和修改等。 - 而
sample_userType.cpp 示范了format库中用户自定义类型的行为。
- sample_formats.cpp
The Boost Format library
<boost/format.hpp>
大纲
一个format对象从一个格式化字符串构造,它以重复的%操作符给出参数。接着每个参数转换成字符串,它们被按照格式合成一个字符串。
cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50;
// prints "writing toto, x=40.230 : 50-th try"
它是如何工作的
- 当你调用format(s),这里s是一个用于格式化的字符串。它从格式化字符串中分析并且查找里面的所有指示并且为下一步准备内部结构。
- 接着,要么马上,像
cout << format("%2% %1%") % 36 % 77 )
format fmter("%2% %1%");
fmter % 36; fmter % 77;
这些变量被存进一个内部stream,它的状态按照格式化字符串中的格式化选项来设定(如果有的话)。Format对象保留最后的字符串结果。 - 一旦是所有的参数都已经传送给格式化对象了,你可以把格式化对象存进一个stream中,可以用str()成员函数得到它的字符串值,或者使用boost命名空间中的自由函数str(const format& )。format对象中的结果字符串保持可取直到另一个参数被传递进来,这时它将重新初始化。
// fmter was previously created and fed arguments, it can print the result :
cout << fmter ;
// You can take the string result :
string s = fmter.str();
// possibly several times :
s = fmter.str( );
// You can also do all steps at once :
cout << boost::format("%2% %1%") % 36 % 77;
// using the str free function :
string s2 = str( format("%2% %1%") % 36 % 77 );
- 可选的,第三步之后,你可以重用format对象并且在第二步重新开始:
fmter % 18 % 39; 用格式化字符串格式化新的变量,保存有关第一步的复杂的处理。
例子
using namespace std;
using boost::format;
using boost::io::group;
- 重新排列的简单输出:
cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style.
- 更精确的格式化,带 Posix-printf 位置指示符:
cout << format("(x,y) = (%1$+5d,%2$+5d) \n") % -23 % 35; // Posix-Printf style
- 经典的 printf 指示符,没有重排:
cout << format("writing %s, x=%s : %d-th step \n") % "toto" % 40.23 % 50;
- 处理同一事情的几种方法:
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;
- 使用操纵子修改格式化串:
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 ;
- 使用带参数的操纵子:
cout << format("_%1%_ %1% \n") % group(showpos, setw(5), 101);
- 新增的格式化特性:'绝对表格', 用在循环中以确保输出的字段在每一行中的相同位置,即使其前面参数的宽度是可变的。
for(unsigned int i=0; i < names.size(); ++i)
cout << format("%1%, %2%, %|40t|%3%\n") % names[i] % surname[i] % tel[i];
names, surnames, 和 tel (见 sample_new_features.cpp) 输出 : Marc-François Michel, Durand, +33 (0) 123 456 789
Jean, de Lattre de Tassigny, +33 (0) 987 654 321
示例文件
程序
sample_new_features.cpp
sample_advanced.cpp
以及
语法
boost::format(
格式化字符串包含的字符文本,在它当中特殊的指示器将被字符串所替换,这些字符串由所给的参数产生。
从c,c++世界继承来的语法其中之一是使用printf,这样format能直接使用printf的格式化字符串,并且产生一样的结果(几乎包含所有方面,细节请查看
这些核心语法已被扩展,来允许新特性,但是同时适合c++流的上下文。所以,format接受在格式化字符串中的一系列的指示:
- 继承自printf的格式化字符串:%spec这里spec是个
printf 格式规则, spec传递格式选项,如宽度,对齐,用来格式化数字的基数。它仅仅在内部stream上设置合适的标志,和/或格式化参数,但是不要求合适的参数为一类特殊的类型。
比如:2$x这样的规格,对于printf意味着“打印参数数字2,它是个16进制的整数”,对于format来说仅仅意味着“打印基于16进制的第二个参数”。 - %|spec|
这里spec是一个打印格式规则。
管道符前面已经介绍过了,它是为了增强格式化字符串的可读性,但是重要的是,使在spec中的类型转换字符任意可选。这些信息对于c++变量来说是不够的,但是为了直接支持printf语法,有必要总是给出一个类型转换字符,仅仅是因为这个字符用来判断格式化字符串的末尾是至关重要的。
比如:"%|-5|"将下一个变量设置为宽度为5,左对齐,就像"%-5g", "%-5f", "%-5s" ..这样的printf格式方式。 - %N%
这个简单的位置标记需要第N个参数的格式化―――而不需要任何格式化选项。
(它不过是printf的位置标志的一个简单表示(就像“%N$s”),但是一个主要的好处是它有更强的可读性,而且不用一个“类型转换”的字符)。
printf 格式化规则
Boost.format所支持的格式化规则严格按照Unix98的文档
注意:在同一个格式化字符串中同时使用有位置信息(e.g. %3$+d)的格式化规则和没有位置信息(e.g. %+d)的格式化规则是会出错的。
在Open-group规则中,使用同一参数多次(e.g. "%1$d %1$d")会导致未定义的行为。Boost.format在这些情况中允许多次使用同一参数。唯一的约束是它期望准确的参数个数,P是格式化用到的最大参数个数(e.g., for "%1$d $d", P = 10)。
如果提供多余或者少于P数量的参数会引起异常(除非它被另外设定,查看
一个规范的spec有这样的格式: [
方括号中的参数是可选的。下面将一一介绍每个参数域:
- N $(可选域)指定了应用于第N个参数的格式规则。(它称之为一个位置格式规则)如果这不满足,参数将被一一提取(并且将会在后面提供参数数时产生一个错误)。
- 标志为下面序列中的任一:
标志 含义 在内部stream的作用 '-' 左对齐 N/A (作用于后面的string) '=' 居中对齐 N/A (作用于后面的string)
-注意 : 新增的特性,printf中是没有的 - '_' 内部对齐 设置内部对齐
-注意:新增特性,printf中没有 - '+' 显示符号无论是否正数 设置showpos '#' 显示数字的基,和小数点 设置showbase 和showpoint '0' 后加0 (插在符号或者基指示器后) 如非左对齐, 调用setfill('0') 并且设置internal
当stream转换成user-defined output 后执行额外的操作. ' ' 如果字符串不是以+,-开头,在已转换的字符串前插入空格 N/A (作用于后面的string)
不同于printf的行为:它不受内部对齐的影响 - width指定转换而来的字符串的最小宽度。如果有必要,字符串将被对齐填补并且通过用操纵子填充设置在stream上字符或者被格式化字符串指定的字符(比如,标志‘0’,‘-’,..)
注意长度不仅设置在转换stream上。为了支持user-defined types 的输出(可能对多个成员多次调用<<操作),宽度在stream将整个参数对象转换之后被传递进去,它是在format类中的代码实现的。 - precision(通过加小数点),设置stream的精度。
- 当输出一个浮点类型数字时,它设置数字的最大数字个数
- 当它是确定的或者科学计数模式时在小数点之后
- 当它是默认模式时为总数(‘普通模式’,像%g)
- 当使用字符类型s或者S时,它包含了另外的含义:转换字符串被压缩成第一类字符的精度。(注意在压缩之后才将后面的填充到width宽度)
- 当输出一个浮点类型数字时,它设置数字的最大数字个数
- 类型字符。它没有强迫相关的参数来为一个限制的类型集合,只是为相关的类型规则设置标志。
字符类型 含义 对stream的作用 p or x 十六进制输出 设置 hex o 八进制输出 设置 oct e 科学计数浮点格式 设置浮点域位为 scientific f 固定浮点格式 设置浮点域位为 fixed g 默认浮点格式 所有浮点域位归位 X, E or G 跟小写一样,但是使用大写字符作为输出(exponents, hex digits, ..) 除了大写,跟小写‘x’,‘e’,‘g’一样 d, i or u decimal 类型输出 设置进制位为 dec s or S 字符串输出 精度格式归位,它的值传给内部域并作用后面的truncation(查看前面的precision注释) c or C 单字符输出 只使用转换字符串的第一个字符 % 输出字符 % N/A 注意‘n’类型规则被忽略(相关的参数也一样),因为它不符合这里的内容。同样的,printf的‘l’,‘L’或‘h’(来表示宽度、long或short类型)支持修改符(对于内部流没有影响)。
新的格式规则
- 就像标志表所列的那样,新加了中间对齐的标志(‘
= '和‘_')。 - %{nt}
, 就像标志表所列的那样,新加了中间对齐的标志(‘ = '和‘_')。(见 examples例子 ) - %|nTX|
以同样的方式插入一个制表符,但是使用X作为填充字符以取代当前流的‘fill’域所指示的字符(默认为空格)。
与 printf 不兼容的地方
假设你有变量 x1,x2(c 的 printf 所支持的内建类型 ), 一个格式化字符串 s 取代下面的 prinft 函数用法 :printf(s, x1, x2);
在几乎所有情况下,结果将和下面的命令一样:
cout << format(s) % x1 % x2;
但是因为有些printf格式化规则没有对应到stream格式化选项中,Boost.format与printf比有一些明显的不足之处。无论如何,format类应该悄悄的忽略所有不支持的选项,这样printf格式化串总是能被format接受并输出几乎和printf一样的结果。
下面完整地列出了它们的不同之处:
- '0'
和 ' ' 选项:对于数字转换printf忽略这些选项,但是format把它们应用于所有的变量类型。(所以把它们应用于自定义的类型上是可行的,比如一个Rational类,等) - 对于printf来说精度对所有的类型参数有特殊的意义:
printf( "(%5.3d)" , 7 ) ;prints « ( 007) »
格式化时像stream一样,对所有的类型忽略精度参数。 - printf选项(用成千上万的字符集合来格式化)对于format没有作用。
- 宽度或精度设置位星号(*)printf通过从参数读取它来设置。比如:printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
这个类目前还不支持这样的机制。所以这样的精度或宽度会悄悄的忽略掉。
format formatter("%+5d");
cout << formatter % x;
unsigned int n = formatter.size();
用户自定义类型的输出
对stream上所有标志的更改都会递归的作用在用户自定义的类上。(标志将保持活动状态,同时期望的format选项也一样,因为’<<’操作可能被用户自定义的类调用)
比如:有一个 Rational 类,我们能这样使用:Rational ratio(16,9);
cerr << format("%#x \n") % ratio; // -> "0x10/0x9 \n"
对于其他的格式化选项是另外一回事。比如,对于设置宽度应用于对象产生的最终输出,并非每个内部输出,这是幸运的:
cerr << format("%-8d") % ratio; // -> "16/9 " and not "16 /9 "
cerr << format("%=8d") % ratio; // -> " 16/9 " and not " 16 / 9 "
0和' '选项也是一样(对应于’+’它用showpos直接改变stream的状态。但是printf没有相应于0和空格的选项)那样不是很自然:
通过仔细设计 Rational 的 << 操作来通过它自己传递流的宽度,对齐和 showpos 参数获取更好的行为是可能的。它在cerr << format("%+08d \n") % ratio; // -> "+00016/9"
cerr << format("% 08d \n") % ratio; // -> "000 16/9"
操纵子和流内部状态
Format的内部流状态被预先保存并在参数输出后复原;因此,修改量不持久且只应用于一个参数。流的默认状态,以标准状态,是:精度为6,宽度为0,并且decimal标志被设定。
通过参数format流内部状态能被操纵子所修改,通过group函数,像:
cout << format("%1% %2% %1%\n") % group(hex, showbase, 40) % 50; // prints "0x28 50 0x28\n"
当传递’group’中的N项,Boost.format需要执行从不同于正则表达式的参数操纵子,因此使用group受到下面的约束:
- 要被打印的对象必须作为group中的最后一项
- 开始的N-1项作为操纵子来对待,如果它们确实产生输出,它被丢弃
这样的操纵子每次在下面的参数前被传递给流。注意格式化选项通过以这种形式传入的重写了的流状态修改器被格式化字符串内部所解释。比如下例所说,格式化字符串中hex控制器拥有比d类型描述更高的优先级,它会设置小数输出:
cout << format("%1$d %2% %1%\n") % group(hex, showbase, 40) % 50;
// prints "0x28 50 0x28\n"
选择
- printf
是经典的选择,这不是类型安全的,对于用户自定义的类型也是无法扩展的 - Karl Nelson设计的ofrstream.cc对于本format类的设计是很大激励
- James Kanze的库有一个format类(在srcode/Extended/format中)它看起来很优美。它的设计同这个类使用内部的来执行实际的转换大致相同,都使用操作符来传递参数。(但是这个类,像ofrstream,使用操作符<<而非操作符%)
- Karl Nelson's library
在讨论Boost库设计Boost.format时作为参考解决方案。
异常
Boost.format
当format发现下面中的一条规则不满足时,它会引出一个相关的异常,因此错误不会被忽略和不被处理。但是用户可以修改这个行为来满足他的需要,当异常发生时使用下面的函数来选择一个错误:
unsigned char exceptions(unsigned char newexcept); // query and set
unsigned char exceptions() const; // just query
用户可以通过使用布尔算术计算来合并下面项从而计算参数
- boost::io::bad_format_string_bit
通过错误形式的格式化字符串来选择错误。 - boost::io::too_few_args_bit
在参数传递前通过查询字符串结果来选择错误。 - boost::io::too_many_args_bit
通过传递过多的参数来选择错误。 - boost::io::out_of_range_bit
通过当调用modify_item或其他使用一个索引项的函数而超出用户提供的索引值范围来选择错误。 - boost::io::all_error_bits
选择所有的错误。 - boost::io::no_error_bits
不选择错误。
举例,如果你不想要Boost.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;
}
如果我们在所有参数提供前请求结果,结果的相关部分简单的为空cout << my_fmt(" %1% %2% \n") % 1 % 2 % 3 % 4 % 5;
cout << my_fmt(" _%2%_ _%1%_ \n") % 1 ;
// prints " __ _1_ \n"
关于性能的注意点
用伴随重排序的boost.format来格式化一些内建的类型的性能可以跟Posix-printf做比较,等价的流手工操作给出了对代价的测量。最终结果很大程度上取决于编译器,标准库实现以及格式化字符串和参数的精度选择。
由于普通流实现最终是要调用printf家族函数来实现格式化,大致上printf将会比直接流操作更快。这主要是由于在重排开销(分配空间以保存字符串片段,每项格式化时的流初始化, ..)上,直接的流操作会比 boost::format 快(你可期望比例在 2 到 5 倍或更多)。
当迭代格式化成为性能瓶颈时,可以通过把格式化字符串分析为一个格式化对象和每次格式化时拷贝它来提高性能。如下所示。
const boost::format fmter(fstring);
dest << boost::format(fmter) % arg1 % arg2 % arg3 ;
作为性能结果的例子,作者用四个方法测量了迭代个格式化的时间
- posix printf
- 手动流输出(到一个虚假的空流中,把传进去的字节忽略掉)
- boost::format
从const对象拷贝,如上所示。 - 直接使用
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 mode :
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 mode :
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
基本原理
这个类的目标是引入一个更好的,c++的,类型安全而且可扩展的等价于printf而能为stream所用的类。
准确的说,format被设计来提供以下的特性:- 支持定位的参数(国际化的需要)
- 接受不限制个数的参数
- 使格式化命令看起来自然
- 支持操纵子的使用来修改显式参数。加入到格式化串语法
- 通过依赖于流上的实际传唤接受任何类型的变量。特别是用户自定义的类型,对这样的类型来说格式化选项作用会很直观自然
- 提供printf兼容,尽可能的使它类型安全和类型可扩展
在设计的过程中面对很多争议,作出了一些选择。这些未必是直观上很正确。但是任何一种情况它们被采用总是
Credits
The author of Boost format is Samuel Krempp.
Revised
Copyright © 2002 Samuel Krempp
Distributed under the Boost Software License, Version 1.0. (See accompanying file