在文字和數字間轉換:boost::lexical_cast


這一篇是來大概介紹一下 Boost C++ Libraries 裡的 lexical_cast 這個函式庫(官網);他的功能相當簡單,主要就是提供了一個 template 函式的介面,來做到任一型別和文字間的轉換。

很多時候,在寫程式的時候,我們會需要把字串文字(例如 std::string)的資料轉成數值(例如:double),或是反過來,把數值資料轉成文字;尤其是當要從檔案讀取資料,或是要求使用者輸入時,基本上這都是會常常要用到的功能。

雖然在標準的 C 或 C++ 裡的確有提供部分的函式、例如 atoi()參考),可以快速地做到部分的轉換,但是其實都有相當的限制。例如基本上他們只針對內建的部分型別有提供對應的函式(應該是只有 intlongdouble),而且也沒有反向的轉換(itoa() 並非標準函式,要用標準的寫法要用 sprintf()參考);而另外由於不同的型別就是不同的函示、也很難寫出統一個方法,來針對轉換的型別做擴充。

使用 C++ IO Stream

而在標準 C++ 的函式庫裡,實際上他有透過 IO Stream 的概念(參考),提供了 stringstream 這個類別(參考),來進行可自訂的、和文字間的轉換。像下面就是一個簡單的例子:

#include <stdlib.h>
#include <string>
#include <sstream>
#include <iostream>
 
using namespace std;
 
int main( int  argc, char** argv )
{
  // string to float
  string sTmp = "123.456";
  stringstream mSS( sTmp );
  float fTmp;
  mSS >> fTmp;
  cout << "Convert from string to float: " << fTmp << endl;
 
  // float to string
  fTmp = 1234.567f;
  stringstream mSS2;
  mSS2 << fTmp;
  sTmp = mSS2.str();
  cout << "Convert from float to string: " << sTmp << endl;
}

第一段的程式碼,基本上就是將 sTmp 這個字串,透過 stringstreamoperator>> 來轉換為浮點數 fTmp;而第二段程式碼,則是反過來,把 fTmp 這個浮點數,轉換成字串、儲存到 sTmp 這個字串裡。基本上,這邊主要就是透過 stringstream 這個「字串流」來做;而他基本的用法,其實和標準輸入、輸出的 cout / cin 是一樣的~差別只在於,他的資料來源和輸出結果,都是字串而已。

而這邊用的範例是比較簡單,直接用內建的 floatstring 這兩種型別來作範例;而如果是要擴充到其他的自訂型別的話,只要去定義各種型別的 operator<<operator>> 就可以了~下面是一個簡單的範例:

class CVector2
{
public:
  float x;
  float y;
};
 
istream& operator>>( istream& iS, CVector2& v )
{
  iS >> v.x >> v.y;
  return iS;
}
 
ostream& operator<<( ostream& oS, const CVector2& v )
{
  oS << v.x << "/" << v.y;
  return oS;
}

這個範例裡定義了一個 2D 的向量 CVector2、裡面有 xy 兩個 float 的成員變數;另外,也定義了 operator<<operator>> 這兩個全域函式,可以用來處理 CVector2 這個自訂型別與 STL stream 間的運作。例如下面就是簡單的範例:

string sTmp = "12 34";
stringstream mSS( sTmp );
CVector2 v;
mSS >> v;
cout << v << endl;

也就是在針對 CVector2 寫好 operator<<operator>> 後,他就擁有標準的 C++ IO stream 的處理能力了!這點算是相當方便的~

 

boost::lexical_cast

不過,如果要直接使用 stringstream 來做的話,其實在操作上會比較繁瑣,所以 Boost C++ Libraries 就提供了所謂的「lexical_cast」,專門來處理這些各種型別和字串間的轉換。

在使用 lexical_cast 時,必須要先加入「#include <boost/lexical_cast.hpp>」;而它的使用基本上非常簡單,就是一個 template 函式,他的形式是:

template<typename Target, typename Source>
Target lexical_cast(const Source &arg);

而使用上,如果直接修改這篇文章第一個使用 stringstream 的程式的話,就會變成下面的形式。

// string to float
string sTmp = "123.456";
float fTmp = boost::lexical_cast<float>( sTmp );
cout << "Convert from string to float: " << fTmp << endl;
 
// float to string
fTmp = 1234.567f;
sTmp = boost::lexical_cast<string>( fTmp );
cout << "Convert from float to string: " << sTmp << endl;

基本上,只要把要轉換的資料當參數傳進去,在指定要轉換的目標型別,就可以了!他省去了所有對於 stringstream 的操作,而直接用一個統一的函式來做,在許多時候會是相當方便的~

而如果要讓自訂的型別也可以適用於 lexical_cast 的話,來源的型別(Source)和目標的型別(Target)分別需要符合一些條件:

  • 來源型別必須要是「OutputStreamable」,也就是有定義 std::ostreamstd::wostreamoperator<<
  • 目標型別必須要是「InputStreamable」,也就是要有針對 std::istreamstd::wistream 定義 operator>>
  • 目標型別必須要有 default constructor 和 copy constructor。

所以基本上,只要有針對自己定義的型別,也定義出 IO Stream 的 operator<<operator>>,應該就可以用了!

一些小細節

不過實際上 Heresy 在用的時候,還是有遇到一些問題。像以前面 CVector2 的例子來說,雖然已經有定義了 operator<<operator>>,但是還是會有問題的!像下面的例子,想直接使用 lexical_cast 把字串 sTmp 轉換成為 CVector2 的話,語法雖然沒問題,但是在執行時是會出錯的!

string sTmp = "12 34";
CVector2 v = boost::lexical_cast< CVector2 >( sTmp );

主要的原因呢,應該是由於 Boost 的 lexical_cast 會去設定使用的 istreamnoskipws、不忽略空白(參考);而也因此,才會造成 istream& operator>>( istream& iS, CVector2& v ) 這個函式在直接使用 stringstream 的時候因為預設會略調空白的資料、而可以正常運作,但是使用 lexical_cast 時卻因為特別把空白也抓出來做處理了,反而造成轉換的錯誤。

而解決方法呢?其實也滿簡單的,只要在進行處理前,先強制設定回 skipws 應該就可以了~修正後的函式,也就變成了:

istream& operator>>( istream& iS, CVector2& v )
{
  iS >> std::skipws >> v.x >> v.y;
  return iS;
}

理論上,這樣應該就不會有問題、可以直接用 lexical_cast 來處理自訂的 CVector2 了~

不過另外要注意的是,實際上 Boost 的 lexical_cast 在做處理的時候,做了很多例外狀況的判斷、處理,算是相當嚴謹的;而也因此,他的效能並不會特別好!有興趣的話,可以參考看看《Very poor boost::lexical_cast performance》這串討論。不過基本上,由於它的效率真的比較差一些,所以如果是轉換的效能很重要的話,建議還是換比較快的方法來寫會比較合適。


Boost C++ Libraries 目錄
廣告

關於 Heresy
https://kheresy.wordpress.com

6 Responses to 在文字和數字間轉換:boost::lexical_cast

  1. yyyy says:

    假如轉換會成為效能上的瓶頸
    或許可以考慮一下boost::spirit
    靈活,高效,簡潔
    不過編譯時間有時候長得讓人想砸熒幕(i3 + 4Gram + win7 64bits + vc2010)
    boost::spirit::karma還有些小bugs,有些bugs下一版的boost會修正
    樓主有興趣的話可以研究一下

    按讚數

    • Heresy says:

      這個 Heresy 倒是還沒碰過,有空再來試試看。
      感謝推薦~

      按讚數

  2. cmchao says:

    之前用也是碰到跟一樓一樣的問題,strtol 還是比好用,通吃

    按讚數

    • Heresy says:

      基本上,strtol() 這類的函式用起來的確也不錯,但是它是針對特定型別寫的,在某些狀況下,就沒有那麼合適了~

      按讚數

  3. chenwj says:

    lexical_cast 無法 convert hex string to signed integer,這部分要使用 stringstream。

    unsigned int x = lexical_cast(“0x0badc0de"); // throw bad_lexical_cast

    這個資訊或許可以補充在您的文章裡。: )

    按讚數

    • Heresy says:

      感謝您的經驗分享。Heresy 自己沒有在用 HEX 的資料形式,所以倒是沒有注意到這個問題。

      按讚數

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: