在CSDN論壇上經(jīng)常看到詢問如何在字符串類型和數(shù)值類型間進(jìn)行轉(zhuǎn)換的問題,也看到了許多不同的答案。下面先討論一下從字符串類型到數(shù)值類型的轉(zhuǎn)換。
- 如何將字符串"123"轉(zhuǎn)換為int類型整數(shù)123?答案是,用標(biāo)準(zhǔn)C的庫函數(shù)atoi;
- 如果要轉(zhuǎn)換為long類型呢?標(biāo)準(zhǔn)C的庫函數(shù)atol;
- 如何將"123.12"轉(zhuǎn)換為double類型呢?標(biāo)準(zhǔn)C的庫函數(shù)atod;
- 如果要轉(zhuǎn)換為long double類型呢?標(biāo)準(zhǔn)C的庫函數(shù)atold;
- ……
后來有朋友開始使用標(biāo)準(zhǔn)庫中的string類,問這個(gè)如何轉(zhuǎn)換為數(shù)值?有朋友答曰,請先轉(zhuǎn)換為const char*。我很佩服作答者有數(shù)學(xué)家的思維:把陌生的問題轉(zhuǎn)化成熟悉的問題。(曾經(jīng)有一則笑話,好事者問數(shù)學(xué)家:知道如何燒水嗎?答:知道。把水壺加滿水,點(diǎn)火燒。又問:如果水壺里已經(jīng)有水了呢?答:先倒掉,就轉(zhuǎn)化為我熟悉的問題了……)
不,不,這樣是C的做法,不是C++。那么,C++該怎么做呢?使用Boost Conversion Library所提供的函數(shù)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;
}
一個(gè)函數(shù)就簡潔地解決了所有的問題。
3.2 數(shù)值→字符串
那么從數(shù)值類型到字符串類型呢?
用itoa?不對吧,標(biāo)準(zhǔn)C/C++里根本沒有這個(gè)函數(shù)。即使在Windows平臺下某些編譯器提供了該函數(shù)3,沒有任何移植性不說,還只能解決int類型(也許其他函數(shù)還可以解決long、unsigned long等類型),浮點(diǎn)類型又怎么辦?當(dāng)然,辦法還是有,那就是:sprintf。
char s[100];
sprintf(s, "%f", 123.123456);
不知道諸位對C里的scanf/printf系列印象如何,總之阿炯我肯定記不住那些稀奇古怪的參數(shù),而且如果寫錯(cuò)了參數(shù),就會得到莫名其妙的輸出結(jié)果,調(diào)試起來可就要命了(我更討厭的是字符數(shù)組,空間開100呢,又怕太小裝不下;開100000呢,總覺得太浪費(fèi),心里憋氣,好在C++標(biāo)準(zhǔn)為我們提供了string這樣的字符串類)。這時(shí)候,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 異常
如果轉(zhuǎn)換失敗,則會有異常bad_lexical_cast拋出。該異常類是標(biāo)準(zhǔn)異常類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”并不能轉(zhuǎn)換為一個(gè)int類型的數(shù)值,于是拋出異常,捕捉后輸出“bad lexical cast: source type value could not be interpreted as target”這樣的信息。
3.4 注意事項(xiàng)
lexical_cast依賴于字符流std::stringstream(會自動引入頭文件4),其原理相當(dāng)簡單:把源類型讀入到字符流中,再寫到目標(biāo)類型中,就大功告成。例如
int d = boost::lexical_cast<int>("123");
就相當(dāng)于
int d;
std::stringstream s;
s<<"123";
s>>d;
既然是使用了字符流,當(dāng)然就有些隨之而來的問題,需要特別指出5。
- 由于Visual C++ 6的本地化(locale)部分實(shí)現(xiàn)有問題,因此如果使用了非默認(rèn)的locale,可能會莫名其妙地拋出異常。當(dāng)然,一般情況下我們并不需要去改變默認(rèn)的locale,所以問題不是很大。
- 輸入數(shù)據(jù)必須“完整”地轉(zhuǎn)換,否則拋出bad_lexical_cast異常。例如
int i = boost::lexical_cast<int>("123.123"); // this will throw
便會拋出異常。因?yàn)椤?23.123”只能“部分”地轉(zhuǎn)換為123,不能“完整”地轉(zhuǎn)換為123.123。
std::string s = boost::lexical_cast<std::string>(123.1234567);
以上語句預(yù)想的結(jié)果是得到“123.1234567”,但是實(shí)際上我們只會得到“123.123”,因?yàn)槟J(rèn)情況下std::stringstream的精度是6(這是C語言程序庫中的“前輩”printf留下的傳統(tǒng))。這可以說是boost::lexical_cast的一個(gè)bug。怎么辦呢?權(quán)宜之計(jì),可以這么做:打開頭文件<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())
//...
}
即可得到正確結(jié)果。當(dāng)然,理論上效率會有一點(diǎn)點(diǎn)損失,不過幾乎可以忽略不計(jì)。
4 小結(jié)
我們已經(jīng)體驗(yàn)了boost::lexcial_cast。當(dāng)然,lexical_cast不僅僅局限于字符串類型與數(shù)值類型之間的轉(zhuǎn)換:可在任意可輸出到stringstream的類型和任意可從stringstream輸入的類型間轉(zhuǎn)換。這次的了解盡管很粗略,不過畢竟我們已經(jīng)“走進(jìn)Boost”,而不僅僅是“走近”。以后,我們可以自行領(lǐng)略Boost的動人之處啦。