C/C++的格式化输出(5)--format文档

boost::format主要有三块组成:构造函数,操作符%,操作符<<
构造函数完成格式解析
操作符%完成参数的处理
操作符<<完成字符串的输出
(未完)

Boost Format 库

format库提供了一个把参数格式化到一个字符串格式的类,就像printf所做的,但是有两个主要的不同:

  • format将参数发送给合适的stream,所以它是完全类型安全的并且自然地支持所有的用户自定义的类型。
  • format强类型转换中省略号不能被正确使用,需要不确定参数的函数被连续调用操作符%来替代。


下面提供详细信息:

The Boost Format library

<boost/format.hpp> 中的format类提供了类'printf'的格式化,它以类型安全的方式实现允许输出用户自定义的类型。


大纲

一个format对象从一个格式化字符串构造,它以重复的%操作符给出参数。接着每个参数转换成字符串,它们被按照格式合成一个字符串。

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto, x=40.230 : 50-th try"

它是如何工作的

  1. 当你调用format(s),这里s是一个用于格式化的字符串。它从格式化字符串中分析并且查找里面的所有指示并且为下一步准备内部结构。
  2. 接着,要么马上,像
    cout << format("%2% %1%") % 36 % 77 )
    或者迟些,像
    format fmter("%2% %1%");
    fmter % 36; fmter % 77;
    你将变量传送给格式化器。
    这些变量被存进一个内部stream,它的状态按照格式化字符串中的格式化选项来设定(如果有的话)。Format对象保留最后的字符串结果。
  3. 一旦是所有的参数都已经传送给格式化对象了,你可以把格式化对象存进一个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 );

  4. 可选的,第三步之后,你可以重用format对象并且在第二步重新开始: fmter % 18 % 39; 用格式化字符串格式化新的变量,保存有关第一步的复杂的处理。
总而言之, format 类把一个格式化字符串转换成(最后使用类 prinf 指示)对一个内部 stream 的操作,最后以字符串格式返回,或者直接输出到一个输出流。

例子

using namespace std;
using boost::format;
using boost::io::group;
  • 重新排列的简单输出:
    cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "333"; // 'simple' style.

    输出 : "11 22 333 22 11 \n"
  • 更精确的格式化,带 Posix-printf 位置指示符:
    cout << format("(x,y) = (%1$+5d,%2$+5d) \n") % -23 % 35;     // Posix-Printf style

    输出 : "(x,y) = ( -23, +35) \n"
  • 经典的 printf 指示符,没有重排:
    cout << format("writing %s,  x=%s : %d-th step \n") % "toto" % 40.23 % 50; 

    输出 : "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;

    输出均为 : "(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 ;

    输出均为 : "_ +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];

    对于一些 std::vector namessurnames, 和 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_formats.cpp 示例了format的一些简单应用。

sample_new_features.cpp 举例说明新增的格式化特征,它们以prinft的语法格式,比如简单定位,中间对齐和制表符功能。

sample_advanced.cpp 举例说明format高级特性,比如重用,修改格式化对象等等。

以及 sample_userType.cpp 示例了对于用户自定义类型中format库的行为。


语法

boost::format( format-string ) % arg1 % arg2 % ... % argN

格式化字符串包含的字符文本,在它当中特殊的指示器将被字符串所替换,这些字符串由所给的参数产生。
cc++世界继承来的语法其中之一是使用printf,这样format能直接使用printf的格式化字符串,并且产生一样的结果(几乎包含所有方面,细节请查看 Incompatibilities with 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 格式化规格之上,新的特性被增加进来了,比如居中对齐。细节参看   new format specification  .

printf 格式化规则

Boost.format所支持的格式化规则严格按照Unix98的文档 Open-group printf,而非标准cprintf,它不支持有位置信息的参数。(普通的标志在前两者中有同样的含义,这样都不会任何人带来麻烦)。
注意:在同一个格式化字符串中同时使用有位置信息(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数量的参数会引起异常(除非它被另外设定,查看 exceptions异常 一节)



一个规范的spec有这样的格式: [ N$ ] [ flags ] [ width ] [ . precision type-char

方括号中的参数是可选的。下面将一一介绍每个参数域:

  • 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 udecimal 类型输出设置进制位为 dec
    s or S字符串输出精度格式归位,它的值传给内部域并作用后面的truncation(查看前面的precision注释)
    c or C单字符输出只使用转换字符串的第一个字符
    %输出字符 %N/A

    注意‘n’类型规则被忽略(相关的参数也一样),因为它不符合这里的内容。同样的,printf的‘l’,L’或‘h’(来表示宽度、longshort类型)支持修改符(对于内部流没有影响)。

新的格式规则

  • 就像标志表所列的那样,新加了中间对齐的标志( = '和‘_')
  • %{nt} 就像标志表所列的那样,新加了中间对齐的标志( = '和‘_')(见 examples例子 )
  • %|nTX| 以同样的方式插入一个制表符,但是使用X作为填充字符以取代当前流的‘fill’域所指示的字符(默认为空格)。

与 printf 不兼容的地方

假设你有变量 x1,x2(c printf 所支持的内建类型 ), 一个格式化字符串 s 取代下面的 prinft 函数用法 :
printf(s, x1, x2);

在几乎所有情况下,结果将和下面的命令一样:
cout << format(s) % x1 % x2;

但是因为有些printf格式化规则没有对应到stream格式化选项中,Boost.formatprintf比有一些明显的不足之处。无论如何,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);
    这个类目前还不支持这样的机制。所以这样的精度或宽度会悄悄的忽略掉。
同样的,注意到特殊的 ’n’ 类型规则(用来告诉 printf 保存 format 输出的字符数)在 format 中没有用。所以格式化字符串包含这个类型规则要产生跟用 printf format 转换来的同样的字符串。它不会导致 printf format 格式化来的字符串的不同。使用 boost.format 来取得格式化字符串字符数,你可以用 size() 成员函数:
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和空格的选项)那样不是很自然:

cerr << format("%+08d \n")  % ratio;  // -> "+00016/9"
cerr << format("% 08d \n") % ratio; // -> "000 16/9"
通过仔细设计 Rational << 操作来通过它自己传递流的宽度,对齐和 showpos 参数获取更好的行为是可能的。它在   sample_userType.cpp   中有举例说明。

操纵子和流内部状态

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受到下面的约束:

  1. 要被打印的对象必须作为group中的最后一项
  2. 开始的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 遵循一套使用格式化对象的规则。格式化字符串遵守上面描述的语法,用户在最终输出目标前必须提供准确的参数数,如果使用modify_item  bind_arg,选项和参数索引不能超出范围。
format发现下面中的一条规则不满足时,它会引出一个相关的异常,因此错误不会被忽略和不被处理。但是用户可以修改这个行为来满足他的需要,当异常发生时使用下面的函数来选择一个错误:

unsigned char exceptions(unsigned char newexcept); // query and set
unsigned char exceptions() const; // just query

用户可以通过使用布尔算术计算来合并下面项从而计算参数 newexcept :

  • 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 ;

作为性能结果的例子,作者用四个方法测量了迭代个格式化的时间

  1. posix printf
  2. 手动流输出(到一个虚假的空流中,把传进去的字节忽略掉)
  3. boost::format const对象拷贝,如上所示。
  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 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兼容,尽可能的使它类型安全和类型可扩展

在设计的过程中面对很多争议,作出了一些选择。这些未必是直观上很正确。但是任何一种情况它们被采用总是 some reasons 的。


Credits

The author of Boost format is Samuel Krempp.   He used ideas from Rüdiger Loos' format.hpp and Karl Nelson's formatting classes.


Valid HTML 4.01 Transitional

Revised 02 December, 2006

Copyright © 2002 Samuel Krempp

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)



  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mengyoufengyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值