在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。怎么辦呢?權宜之計,可以這么做:打開頭文件<boost/lexical_cast.hpp>,注意對照修改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的動人之處啦。