0 摘要 一直流傳這麼一個說法,想成為高手,一定要多讀高手寫的原始碼。哪些代碼是好材料呢?C++標準庫的原始碼?不,如果您讀過,就會發現:要麼是各種實現專屬的表達方式讓人摸不著頭腦,要麼是恐怖的代碼風格(如到處是底線)憋得人難受。Boost庫的代碼則相當清晰,注釋合理,命名規範,絕對是適合閱讀的典範。同時,Boost內容廣泛,數值計算、泛型程式設計、元編程、平台API……不妨從容選擇自己感興趣的部分,細細品味。
在本文中,我們將會介紹了Boost庫的下載與安裝,並將體驗Boost庫中一個非常簡單實用的組件lexcial_cast。
1 Boost簡介 Boost是什嗎?一套開放原始碼、高度可移植的C++庫。
誰發起的?C++標準委員會庫工作群組。所以,品質保證,不怕遇到假冒偽劣產品。
有些什麼呢?瞧瞧:
- Regex,可以與POSIX API和Perl語言處理Regex的功能相媲美,而且還能支援各種字元類型(如char、wchar_t,甚至還可以是自訂字元類型);
- 多線程,想了很久的跨平台多線程庫了;
- 資料結構“圖”,再加上即將加入標準的hash_set、hash_map、hash_multiset、hash_multimap等等(事實上不少STL實作,如SGI STL,已經支援以上資料結構),C++對資料結構的支援已近完備;
- python,沒錯,對Python語言的支援;
- 智能指標,與std::auto_ptr一起善加使用,可杜絕記憶體泄露,效率更不可和垃圾收集機制GC同日而語;
- 更有迴圈冗餘的CRC、可輕鬆定義返回多個值函數的元組tuple、可容納不同類型值的any、對標準庫各方面的補充……
- 還在迅速擴大中,部分內容有望進入C++標準庫……
2 Boost下載和Boost安裝 去哪下載Boost呢?英文http://www.boost.org (1),中文http://boost.c-view.org,可以找到一個.zip或.tar.gz格式的壓縮包。下載完畢後,解壓到某個目錄,比如boost_1_26_0,裡面一般有這麼幾個子目錄:boost、libs、more、people、status、tools,看看沒問題就行了。
如果Boost更新時您懶得去下載整個壓縮包,只希望更新發生變動的檔案;或者您是一位跟我一樣的Boost Fans,希望跟蹤Boost的最新變化,不妨使用CVS方式。首先得有一個CVS用戶端軟體,比如CvsGui或http://sourceforge.net/projects/cvsgui/提供的WinCVS、gCVS和MacCVS,分別適用於Windows、Linux和MacOS平台。下載、安裝、啟動三步曲。
如果您習慣於傳統CVS的命令列模式,那麼可在Admin→Command Line...→Command line settings中輸入下面一行2:
cvs -z3 -d:pserver:anonymous@cvs.boost.sourceforge.net:/cvsroot/boost checkout boost
勾上下面的複選框,選擇本地目標目錄(比如可以建立一個C:/Boost,這憑個人愛好),再點擊確定即可開始更新。如果是第一次運行,則可能需要一段時間下載所有檔案。當然以後更新就只需要很短的時間了。
如果您偏好GUI模式,請選擇Admin→Preferences...,在General的Enter CVS ROOT中填寫:
anonymous@cvs.boost.sourceforge.net:/cvsroot/boost
Authentication 選擇"passwd" file on the cvs server,同時Use version選擇cvs 1.10 (standard)。然後在WinCvs的HOME folder中填寫或選擇一個本地目標目錄,點擊確定。選擇View→Browse Location→Change...換到本地目標目錄後,在Create→Check Module...→Checkout Settings的Enter the module name and path on the server中填寫boost,單擊確定即可。如果這一過程中要求輸入密碼,不必理會,直接斷行符號就行。這是WinCVS 1.2的情況。如果您下載的是新的版本,請注意各項設定大同小異,如前面的Authentication選擇pserver、不需要設定Use version等。
然後設定編譯器。以Windows常用Integration Environment為例。Microsoft Visual C++ 6.0,可在工具→選擇→目錄處把Boost的路徑(如前面的boost_1_26_0)添加到Include Files搜尋路徑中。而對於Borland C++ Builder 5.0,則是在Project→Options→Directories/Conditionals→Include Path中添加Boost的路徑。還有一種比較常用的Dev-C++ 4.0(內建GNU C++,可從http://www.bloodshed.net處免費下載),可在Options→Compile Options→Directories→C++ include files處添加Boost的路徑即可。其他IDE類似。至於命令列方式,則需在編譯時間對相應的標頭檔路徑參數(Borland C++ Compiler、GNU C++是-I,VC++的cl是/I)給出Boost路徑。
做到這一步,恭喜您,大部分Boost庫就可以用了。
為什麼不是全部?首先,目前還沒有一個能完全符合C++標準的編譯器,所以Boost庫中的組件或多或少不可用,詳細資料請看Boost網站上“編譯器支援情況(Compiler Status)”一文。另外,有些庫需要Build相應的lib或dll檔案。不過這樣的庫很少,主要是由於平台相關性的原因,如處理Regex的 regex庫、支援python語言的python庫等,而建構庫的過程相當煩瑣,需要使用Jam工具(可以簡單提一下:在 tools/build/jam_src/builds目錄下有三個檔案win32-borlandc.mk、win32-gcc.mk、win32- visualc.mk,分別是適用於Windows平台下的Borland C++ Compiler、GNU C++和Visual C++的mak檔案。如果在Unix平台,則應使用tools/build/Makefile。用命令列工具make或nmake來做出Jam執行檔案,然後再用Jam來建構庫,詳細內容可見Boost.Build文檔)。我個人的建議是,不用急著去建構lib或dll。真的需要使用這些庫時,再make 隨庫提供的mak檔案即可。雖然Boost.Jam也許是Boost庫未來發展的方向,不過畢竟絕大部分庫都無須建構,可以直接使用。
3 Boost組件lexical_cast 這次我們先挑個簡單實用的Boost組件,看看Boost能給我們帶來怎樣的便利。
3.1 字串→數值
在CSDN論壇上經常看到詢問如何在字串類型和數實值型別間進行轉換的問題,也看到了許多不同的答案。下面先討論一下從字串類型到數實值型別的轉換。
- 如何將字串"123"轉換為int類型整數123?答案是,用標準C的庫函數atoi;
- 如果要轉換為long類型呢?標準C的庫函數atol;
- 如何將"123.12"轉換為double類型呢?標準C的庫函數atod;
- 如果要轉換為long double類型呢?標準C的庫函數atold;
- ……
後來有朋友開始使用標準庫中的string類,問這個如何轉換為數值?有朋友答曰,請先轉換為const char*。我很佩服作答者有數學家的思維:把陌生的問題轉化成熟悉的問題。(曾經有一則笑話,好事者問數學家:知道如何燒水嗎?答:知道。把水壺加滿水,點火燒。又問:如果水壺裡已經有水了呢?答:先倒掉,就轉化為我熟悉的問題了……)
不,不,這樣是C的做法,不是C++。那麼,C++該怎麼做呢?使用Boost Conversion Library所提供的函數lexical_cast(需要引入標頭檔boost/lexical_cast.hpp)無疑是最簡單方便的。如:
#include <boost/lexical_cast.hpp>#include <iostream>int main(){ using boost::lexical_cast; int a = lexical_cast<int>("123"); double b = lexical_cast<double>("123.12"); std::cout<<a<<std::endl std::cout<<b<<std::endl; return 0;}
一個函數就簡潔地解決了所有的問題。
3.2 數值→字串
那麼從數實值型別到字串類型呢?
用itoa?不對吧,標準C/C++雷根本沒有這個函數。即使在Windows平台下某些編譯器提供了該函數3,沒有任何移植性不說,還只能解決int類型(也許其他函數還可以解決long、unsigned long等類型),浮點類型又怎麼辦?當然,辦法還是有,那就是:sprintf。
char s[100];sprintf(s, "%f", 123.123456);
不知道諸位對C裡的scanf/printf系列印象如何,總之阿炯我肯定記不住那些稀奇古怪的參數,而且如果寫錯了參數,就會得到莫名其妙的輸出結果,調試起來可就要命了(我更討厭的是字元數組,空間開100呢,又怕太小裝不下;開100000呢,總覺得太浪費,心裡憋氣,好在C++標準為我們提供了string這樣的字串類)。這時候,lexical_cast就出來幫忙啦。
#include <boost/lexical_cast.hpp>#include <string>#include <iostream>int main(){ using std::string; const double d = 123.12; string s = boost::lexical_cast<string>(d); std::cout<<s<<std::endl; return 0;}
跟前面一樣簡單。
3.3 異常
如果轉換失敗,則會有異常bad_lexical_cast拋出。該異常類是標準異常類bad_cast的子類。
#include <boost/lexical_cast.hpp>#include <iostream>int main(){ using std::cout; using std::endl; int i; try{ i = boost::lexical_cast<int>("abcd"); } catch(boost::bad_lexical_cast& e) { cout<<e.what()<<endl; return 1; } cout<<i<<endl; return 0;}
顯然“abcd”並不能轉換為一個int類型的數值,於是拋出異常,捕捉後輸出“bad lexical cast: source type value could not be interpreted as target”這樣的資訊。
3.4 注意事項
lexical_cast依賴於字元流std::stringstream(會自動引入標頭檔4),其原理相當簡單:把源類型讀入到字元流中,再寫到目標類型中,就大功告成。例如
int d = boost::lexical_cast<int>("123");
就相當於
int d;std::stringstream s;s<<"123";s>>d;
既然是使用了字元流,當然就有些隨之而來的問題,需要特別指出5。
- 由於Visual C++ 6的本地化(locale)部分實現有問題,因此如果使用了非預設的locale,可能會莫名其妙地拋出異常。當然,一般情況下我們並不需要去改變預設的locale,所以問題不是很大。
- 輸入資料必須“完整”地轉換,否則拋出bad_lexical_cast異常。例如
int i = boost::lexical_cast<int>("123.123"); // this will throw
便會拋出異常。因為“123.123”只能“部分”地轉換為123,不能“完整”地轉換為123.123。
std::string s = boost::lexical_cast<std::string>(123.1234567);
以上語句預想的結果是得到“123.1234567”,但是實際上我們只會得到“123.123”,因為預設情況下std::stringstream的精度是6(這是C語言程式庫中的“前輩”printf留下的傳統)。這可以說是boost::lexical_cast的一個bug。怎麼辦呢?權宜之計,可以這麼做:開啟標頭檔,注意對照修改6:
#include <boost/limits.hpp>//...template<typename Target, typename Source>Target lexical_cast(Source arg) { //... Target result; interpreter.precision(std::numeric_limits<Source>::digits10); if( !(interpreter << arg) || !(interpreter >> result) || !(interpreter >> std::ws).eof()) //...}
即可得到正確結果。當然,理論上效率會有一點點損失,不過幾乎可以忽略不計。
4 小結 我們已經體驗了boost::lexcial_cast。當然,lexical_cast不僅僅局限於字串類型與數實值型別之間的轉換:可在任意可輸出到stringstream的類型和任意可從stringstream輸入的類型間轉換。這次的瞭解儘管很粗略,不過畢竟我們已經“走進Boost”,而不僅僅是“走近”。以後,我們可以自行領略Boost的動人之處啦。
5 注釋
[1] 如果您訪問Boost英文網站出現DNS錯誤,不妨試試http://64.226.201.52/。
[2] 請參考Boost文檔中的“下載與安裝說明(Boost Download and Installation)”部分。
[3] Borland C++ Builder提供了itoa,而Microsoft Visual C++提供了一個功能相同的函數,不過名字是_itoa。
[4] 有些不符合標準的標準庫實現中,字元流類名是strstream,在標頭檔中。而標準規定的是stringstream,在標頭檔中。
[5] 請參考http://groups.yahoo.com/group/boost/message/15023的討論。
[6] 非常感謝Andrew Koenig和Bjarne Stroustrup兩位的指教和協助。最開始我的想法是,指定最大精度,加入interpreter.precision(15)之類的語句,然而又擔心移植性的問題。Andrew Koenig先生給出了非常明確的解釋:You are quite correct that 15 is not portable across all floating-point implementations. However, it is portable across all implementations that support IEEE floating-point arithmetic, which is most computers that are in common use today. If you want to do better than that, you might consider using numeric_limits::digits10, which is the number of significant base-10 digits that can be accurately represented in a double.(中文大意是,誠然,15並非可移植到所有浮點實現中,但對於支援IEEE浮點運算的實現來說,則的確是可移植的,而且,這也是現今絕大部分電腦所使用的。如果想做得更好一點,則可以考慮使用numeric_limits::digits10,就能表示出10進位下double能精確表達的位元。)