boost::format[轉(zhuǎn)載]

這篇是之前預(yù)告過的 Boost C++ Libraries 系列文章的第一篇翘县。所介紹的底哥,是在 Boost 裡用來格式化輸出的函式庫:boost::format脉漏。
他最大的特色是在於它可以使用 C 語言中 printf 的格式化字串,來針對 C++ 的 iostream 做輸出颗品、或是產(chǎn)生格式化的字串肯尺;相較於 C++ iostream 的 manipulator,boost::format 在使用上更為直覺抛猫、簡單蟆盹。而且和 printf 不同的地方在於孩灯,他又有 C++ iostream 的 type safe闺金、可以支援自訂類別的輸出的優(yōu)點~
官方網(wǎng)站的介紹可以參考:http://www.boost.org/doc/libs/1_44_0/libs/format/index.html
C printf 與 C++ iostream
一般在 C 語言的時候峰档,大家應(yīng)該都很習(xí)慣用 printf 這個函式(參考)來做輸出的動作败匹。由於 printf 有著強大寨昙、簡單的格式化輸出的能力,所以很多人就算是使用 C++掀亩,也會捨棄較為安全的 iostream(參考)舔哪,而繼續(xù)使用 printf、fprintf槽棍、sprintf 這類的函式來做字串的格式化處理捉蚤、輸出。(話說炼七,Heresy 自己也不完全記得 iostream 要怎麼格式化輸出…)
不過實際上缆巧,C 語言的 printf 在使用上並不是非常地安全。最主要的問題豌拙,在於使用 printf 的時候陕悬,並不是 type safe 的!一個很簡單的例子就是:
char* x = "abcd";printf( "%d", x );
由於在使用 printf 輸出的時候按傅,需要先指定輸出資料的型別(%d捉超、%f 等),所以其實一不小心就有可能弄錯唯绍,變成像上面一樣拼岳,指定了錯誤的輸出型態(tài)。另外推捐,也由於 printf 設(shè)計上的問題裂问,所以如果要輸出自定義的類別資料,會變得相對麻煩牛柒。
而如果是要用 sprintf 這個函式來產(chǎn)生格式化的字串的話堪簿,更有可能產(chǎn)生記憶體使用上的問題,Heresy 之前也有寫過一篇《用 snprintf / asprintf 取代不安全的 sprintf》皮壁,就是在講這部分的東西椭更,有興趣的人可以參考看看。
基本上 C++ 的 iostream 就已經(jīng)解決這些問題了蛾魄。如果是使用 C++ 的 iostream 的話虑瀑,其實在各方面的問題相對都少很多,用起來也相對簡單很多滴须;對於自訂類別的輸出舌狗,更可以用 operator overloading 的方法,來為每一個類別都寫一個屬於自己扔水、並且符合 iostream 使用方法的輸出痛侍。
但是如果提到格式化輸出的部份的話,雖然 C++ 的 iostream 有提供「manipulator」來讓使用者針對輸出的格式做控制(參考)魔市,但是包含 Heresy 自己本身在內(nèi)主届,Heresy 知道有在寫 C++ 程式的人赵哲,好像大多都還是習(xí)慣使用 printf 系列的函式來做,而不會使用 iostream 的 manipulator君丁。畢竟枫夺,在 Heresy 看來,他既不好記绘闷、也不好用啊…

boost::format 基本使用
而 Boost 的 Format 這個函式庫(官方介紹)橡庞,基本上就是為了讓程式設(shè)計師可以更簡單地使用 C++ 的 iostream 來進行格式化輸出而開發(fā)的!如同 Heresy 在一開始就提過的印蔗,boost::format 提供了一個和 C 的 printf 類似的格式化字串(format string)的語法定義毙死,來讓程式開發(fā)者可以非常簡單地做到和 printf 一樣效果的格式化輸出~而同時,他也保有了 C++ 的 iostraem 的各項優(yōu)勢喻鳄,對於要做格式化輸出的 C++ 程式開發(fā)人員來說扼倘,boost::format 應(yīng)該是個相當(dāng)好用、也值得一試的的函式庫除呵!
boost::format 是一個 header-only 的函式庫再菊,只要準(zhǔn)備好 header 檔,不用預(yù)先編譯就可以使用了颜曾,在使用上相當(dāng)?shù)乇憷腊巍6谶@個函式庫裡,主要是提供了一個 format 的類別(註一)泛豪,來讓程式開發(fā)者來做操作稠诲。下面是一個簡單的例子:

#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 相關(guān)的程式了诡曙。首先臀叙,要使用 boost::format,我們必須要先 include「boost/format.hpp」這個檔案价卤;只要 include 了這個檔案後劝萤,就可以使用 boost::format 的功能了。
而 boost::format 最接近 printf 的用法慎璧,也就是上面這樣的形式(POSIX-printf style)了~這樣的寫法在透過 cout 做輸出後的結(jié)果床嫌,會和
printf( "%2.3f, %d", 1.23456, 12 );
完全一樣。
實際上胸私,這邊是使用「"%2.3f, %d"」這個格式化字串厌处,來建立一個 boost::format 的物件,並透過這個物件來做之後格式化的操作岁疼;而這邊所使用的格式化字串阔涉,和使用 printf 時是完全相同的。
而除了上面這種「Posix-Printf style 」以外,也還有所謂的「simple style」(簡單風(fēng)格)的用法可以使用洒敏,下面就是一個簡單的例子:
cout << boost::format( "%1%, %2%" ) % 1.23456 % 12 << endl;
在這種風(fēng)格的寫法中,是在格式化字串裡疙驾,用「%1%」來代表之後的第一個變數(shù)凶伙、用「%2%」來代表第二個變數(shù);透過這樣的定義它碎,我們就可以自行調(diào)整變數(shù)的順序函荣、同時也可以重複地使用某一項變數(shù)了~例如:
cout << boost::format( "%1%, %2%, %1%" ) % 1.23456 % 12 << endl;
這樣寫的話,輸出的結(jié)果就會是「1.23456, 12, 1.23456」扳肛。 不過由於這個寫法沒有特別指定格式化的設(shè)定傻挂,所以所有變數(shù)都會用預(yù)設(shè)的方法做輸出。

boost::format 物件的操作
前面已經(jīng)有提到挖息,boost::format 實際上是一個類別金拒,在使用時實際上會產(chǎn)生一個型別是 boost::format 的物件,來進行後續(xù)的操作套腹;之後所有的變數(shù)绪抛,都是透過呼叫 operator% 的方式,依序傳給這個物件(註二)电禀,最後再透過 operator<< 把他的資料輸出傳給 cout幢码。
相較於此,printf 本身是一個參數(shù)數(shù)目可變(variable-length argument)的函示尖飞,所以所有要輸出的變數(shù)症副,都是用逗號隔開、以函式引數(shù)的方式傳進去的政基。所以這兩者雖然在程式的寫法上看起來很類似贞铣,但是在概念和實作方法上,是完全不同的沮明。
像下面這個例子:
cout << boost::format( "%1%, %2%" ) % 1.23456 % 12 << endl;
實際上可以看成:
cout << ( ( boost::format( "%1%, %2%" ) % 1.23456 ) % 12 ) << endl;
而也由於 boost::format 實際上是以物件的形式在運作的咕娄,所以實際的執(zhí)行過程,就相當(dāng)於:
boost::format fmt( "%1%, %2%" );fmt % 1.23456;fmt % 12;cout << fmt << endl;
這也代表程式開發(fā)者可以把 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)的變數(shù)是會儲存在物件內(nèi)部的,所以可以分批的傳入變數(shù)摧扇;但是如果變數(shù)的數(shù)量不符合的話圣贸,在編譯階段雖然不會出現(xiàn)錯誤,可是到了執(zhí)行階段還是會讓程式當(dāng)?shù)艨富栽谑褂蒙媳仨毿⌒囊稽c吁峻。不過,在有輸出後,是可以再重新傳入新的變數(shù)用含、重複使用同一個 boost::format 物件的矮慕。

透過 boost::format 產(chǎn)生字串
前面提的方法,都是把 boost::format 產(chǎn)生的結(jié)果直接輸出到 ostream 的用法啄骇,那如果是要把格式化輸出的結(jié)果產(chǎn)生成字串繼續(xù)使用呢痴鳄?很簡單,因為 boost 已經(jīng)有提供對應(yīng)的函式可以做這件事了~基本上有兩種方法缸夹,第一個方法是用 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();
這兩者基本上是一樣的痪寻,只是程式的寫法不同罷了。

語法細(xì)節(jié)
前面大概提到了所謂的 POSIX printf style 和 simple style 兩種用法虽惭。實際上 boost::format 所使用的格式化字串的語法橡类,是依照 Unix98Open-group printf,再做一些延伸而定的芽唇;它的形式是:
%[N$][flags][width][.precision]type-char
其中大部分的內(nèi)容都和傳統(tǒng)的 printf 相同(參考)顾画,只有部分不一樣。(註四)
像是在 flags 的部分匆笤,boost::format 除了本來的「-」是向左對齊外亲雪,還多了新的置中對齊的「=」、以及內(nèi)部對齊(internal alignment)的「_」疚膊,這兩者就是 printf 沒有的义辕。而除了可以用「%%」來輸出「%」符號外,也多了可以用「%nt」來填入 n 個空格寓盗、或是用「%|nTX|」來填入 n 個 X(X 為單一字元)的功能灌砖。
此外,也有某些東西的行為會和 printf 不太一樣傀蚌,不過由於比較細(xì)節(jié)基显,在這邊就不贅述了;有興趣的請自行參考《Differences of behaviour vs printf》的部分善炫。
而在實際使用上撩幽,大致應(yīng)該分成下面幾種形式:
%N%:(Simple style)最簡單、沒有格式化的簡化寫法箩艺,其中 N 只是單純標(biāo)記是第幾個變數(shù)窜醉。
%spec:(POSIX-printf style 格式化字串)這部分主要是相容於 printf 的寫法,基本上可以把本來用在 printf 上的寫法直接拿來用艺谆。當(dāng)然榨惰,spec 的部分也有支援 boost::format 額外定義的新東西可以用。
%|spec|:這是用「|」來做分隔的表示方法静汤。spec 基本上和前者是相同的琅催,這種寫法主要的優(yōu)點是可以省略指定型別的字元(printf 裡的「type-conversion character」)居凶,同時也可以加強程式碼的可讀性。例如:「%|-5|」就是代表向左對齊藤抡、寬度是 5侠碧,根據(jù)變數(shù)型別的不同,和「%-5g」缠黍、「%-5f」等是等價的弄兜。
其中,看起來比較特別的寫法嫁佳,或許是「%|1$+4.2|」這樣的形式吧~它代表的意義基本上就是把第一個變數(shù)(1$),以「+4.2」的形式來做輸出谷暮;而這邊也沒有特別指定輸出的型別蒿往,所以在執(zhí)行階段的行為,可能會根據(jù)傳入的變數(shù)的型別而有所不同湿弦。

範(fàn)例
由於 boost::format 的應(yīng)用變化非常地多瓤漏,所以Heresy 在這邊不打算針對 boost::format 舉出太多的範(fàn)例,基本上只由官方的範(fàn)例裡颊埃,挑一組 Heresy 覺得有代表性的出來蔬充,下面就是程式碼:

cout << boost::format("(x,y) = (%+5d,%+5d) ") % -23 % 35;
cout << boost::format("(x,y) = (%|+5|,%|+5|) ") % -23 % 35;
cout << boost::format("(x,y) = (%1$+5d,%2$+5d) ") % -23 % 35;
cout << boost::format("(x,y) = (%|1$+5|,%|2$+5|) ") % -23 % 35;

這四行不同的 boost::format 寫法輸出的結(jié)果都是一樣的,會是:
(x,y) = ( -23, +35)
這組例子也大致上代表 boost::format 幾種不同形式的寫法了~有興趣的話班利,稍微研究一下饥漫,應(yīng)該可以簡單地找到這些寫法間的差異了。

效能問題
boost::format 雖然在使用上算是滿方便的罗标,但是實際上在效能面來說庸队,並不是非常地好,這點在官方的網(wǎng)頁就有特別提出來闯割〕瓜基本上,在一般狀態(tài)下宙拉,使用 printf 的效能會是最好的宾尚,iostream 會比 printf 慢一些,而 boost::format 則由於又有其他的 overhead谢澈,所以又會更慢煌贴。官方也有提供一些測試數(shù)據(jù),如果在 release 模式下锥忿,iostream 的操作所需的時間大約會是 printf 的 1.6 倍崔步;而 boost::format 所需的時間則會是 iostream 的 2 ~ 3 倍左右,也就是大約是 printf 的 3 ~ 5 倍缎谷。
從這個測試數(shù)據(jù)應(yīng)該就可以發(fā)現(xiàn)井濒,其實 boost::format 的效能並不好灶似。所以如果程式本身的效能瓶頸是在這類的字串輸出、處理的話瑞你,那使用 boost::format 可能就不是一個好的選擇酪惭,因為他確實有可能會讓效能變差;所以在這種情況下者甲,最好的方法應(yīng)該還是回去用 printf 了~
不過實際上春感,一般的程式主要的效能瓶頸應(yīng)該不會是在這一部分,所以在這種狀況下使用 boost::format 的話虏缸,應(yīng)該不會對整體效能造成很大的影響鲫懒;相對地,卻有可能因為使用 boost::format 而減少不少開發(fā)時的時間成本刽辙。所以如果是在這種狀況下的話窥岩,boost::format 應(yīng)該還是有相當(dāng)?shù)膶嵱眯缘摹?/p>

結(jié)語
對於 boost::format 的介紹,大概就到這先告一個段落了宰缤。其實颂翼,講的應(yīng)該不算是很完整,有不少細(xì)節(jié)都被 Heresy 跳過了慨灭,只是一個簡單地介紹罷了朦乏。真正要完全學(xué)會的話,可能還是得回官方網(wǎng)站看看了氧骤;相信如果願意花時間的話呻疹,應(yīng)該可以挖到更多進階的用法才對!
另外筹陵,由於 boost::format 的語法定義主要是基於 printf 的語法來做沿伸的诲宇,所以 Heresy 在這邊也就決定跳過了不少相關(guān)的說明;但是實際上在寫這篇文章的過程中惶翻,卻也發(fā)現(xiàn)其實 printf 的格式化字串的語法姑蓝,有不少是 Heresy 之前也沒注意到的、沒搞懂的…或許吕粗,之後還是要再仔細(xì)研究看看吧…

附註
實作上是一個 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 上應(yīng)該是可以用的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞳收,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子厢汹,更是在濱河造成了極大的恐慌螟深,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烫葬,死亡現(xiàn)場離奇詭異界弧,居然都是意外死亡,警方通過查閱死者的電腦和手機搭综,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門垢箕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兑巾,你說我怎么就攤上這事条获。” “怎么了闪朱?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵月匣,是天一觀的道長钻洒。 經(jīng)常有香客問我奋姿,道長,這世上最難降的妖魔是什么素标? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任称诗,我火速辦了婚禮,結(jié)果婚禮上头遭,老公的妹妹穿的比我還像新娘寓免。我一直安慰自己,他們只是感情好计维,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布袜香。 她就那樣靜靜地躺著,像睡著了一般鲫惶。 火紅的嫁衣襯著肌膚如雪蜈首。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天欠母,我揣著相機與錄音欢策,去河邊找鬼。 笑死赏淌,一個胖子當(dāng)著我的面吹牛踩寇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播六水,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼俺孙,長吁一口氣:“原來是場噩夢啊……” “哼辣卒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鼠冕,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤添寺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懈费,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體计露,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年憎乙,在試婚紗的時候發(fā)現(xiàn)自己被綠了票罐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡泞边,死狀恐怖该押,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阵谚,我是刑警寧澤蚕礼,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站梢什,受9級特大地震影響奠蹬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嗡午,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一囤躁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荔睹,春花似錦狸演、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吨拗,卻和暖如春满哪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丢胚。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工翩瓜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人携龟。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓兔跌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親峡蟋。 傳聞我的和親對象是個殘疾皇子坟桅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容