字符串與其餘基本類型的轉換——從C到C++11

轉自 IBM 編譯器中國開發團隊的《C++11中的string - atoi/itoa》

在C++11中,因爲右值引用的引入,常爲人所詬病std::string的性能問題獲得了很大的改善。另一方面,咱們也能夠看到新語言爲std::string類增長了不少新的api。比較引人注意的就是std::string的成員函數stoi系列,以及std::to_string全局函數。這兩種API雖然很不起眼,卻爲C++11的格式化輸出(formatted I/O)增長了一種實用的手段。咱們能夠依序會議一下C,C++98,C++11中咱們是如何處理atoi/itoa的問題的:程序員

在C時代,一般咱們遇到atoi(字符串到數值轉換)的問題的時候咱們會使用<stdlib.h>中的atoi函數:編程

int num = atoi(cstr);api

這裏的cstr一般爲char或者const char類型的字符串。函數返回的結果則是該字符串所表示的一個十進制的integer。函數的整個效果則等同於<stdlib.h>中的另一個函數strtol:數組

int num = strtol(cstr, NULL, 10);安全

相比於atoi,strtol多了最後一個參數"radix"代表函數採用的是幾進制(這個進制數能夠從2到34,這個數值範圍的緣由顯而易見)。除去strtol會在出錯時設置全局的errno外,其效果與atoi系列中的atol則幾乎是徹底等同的。函數

而C時代解決itoa(數值到字符串的轉換)的時候,則採用了sprintf函數:性能

int myint; char buf[SIZE]; sprintf(buf, "my data is %d", myint);學習

這裏字符的輸出控制交給了"%d"這樣的特殊字符。經過特殊字符以及變長參數的配合(sprintf是變長參數函數),咱們得到預期的formatted I/O的輸出。 這裏咱們能夠看到C中對atoi/itoa的處理的特色,基本能夠概括以下:設計

  1. atoi不檢查字符串中錯誤。這對使用API的程序員而言意味着他必須檢查錯誤,或者必須判斷出錯誤在實際使用中老是不存在或者是能夠被程序忍受的。
  2. atoi的替代版本strtol檢查字符串的錯誤,但使用的是POSIX中的標準方式,設置errno。這意味着使用strtol的程序員若是要檢測字符串中的錯誤,須要在調用strtol後檢測全局變量errno。
  3. sprintf不負責任何的內存管理。一般狀況下,程序員都會被告誡使用snprintf或者其它有內存邊界檢查的版本替代sprintf。這樣一來會減小發生緩衝區溢出的可能性。不過總的來講這只是一種編程中的防護手段,從程序員的角度而言,內存管理的煩惱依然存在。
  4. sprintf跟printf同樣,不檢查參數類型(由於是以變長函數的方式實現的),因此若是參數和escape character不匹配的話,會在運行時才發現不匹配的輸出。不過相對於其它三點,這種錯誤是最容易修正的。 因此說C中的atoi/itoa問題的解決方式並算不得讓程序員愉悅。在壞的輸入狀況下,程序員必須當心處理各類異常,以防程序誤入歧途。不過反過來看,C中的atoi/itoa的處理也很是直觀,易於理解,因此即便在C++中這樣的代碼也並不是少見。

到了C++98時代,atoi/itoa可使用新的C++標準庫來完成。具體地就是使用C++的流(stream)模板類。值得注意的是,在C++98代碼中,雖然字符串的存儲使用字符串數組也是徹底能夠的,但在C++代碼中使用std::string類型,內存能夠自行有效地管理,並且成員函數能夠拋出異常,因此更適用於C++代碼。而關於std::string類型的流模板類型就是std::stringstream。經過全局重載的operator <<以及operator >>,std::stringstream能夠很輕鬆地完成atoi或者是itoa的任務,好比:orm

ostringstream oss; oss << 15 << " is int, " << 3.14f << " is float." << endl; cout << oss.str();

oss就是一個字符串流對象,能夠用於itoa的工做。而

istringstream iss("12 14.1f"); int a; float b; iss >> a >> b; cout << a << " " << b << endl;

上面代碼中的iss字符串流對象,則可用做atoi。 從設計上講,std::stringstream算得上是一種好的設計。這是因爲使用std::stringstream的代碼看起來很是地直觀。並且因爲其來自於C++庫,程序員一般也不太關心是否會有exception拋出--由於若是代碼沒有try-catch block的話,exception一旦拋出,程序就會直接直接終止(調用std::terminate)。這種解決出錯的方式對於程序員來講更爲爽快,由於程序在問題點終止,就很容易找到出問題的代碼位置。而C時代的atoi/itoa,如同咱們講到的,須要程序員關注異常,若是漏過處理異常以後(其實這很常見),程序可能帶病運行。固然,因爲stringstream老是"附着"於一個內存能夠自行管理的string對象,因此程序員一般也沒必要擔憂任何的內存分配問題。 從設計角度出發看,std::stringstream幾乎無可挑剔。但在實際使用中,如咱們在上面提到的,不少人仍是願意使用C中的處理方法來完成atoi/itoa。這大概有兩方面的緣由:

  1. std::stringstream在概念上的間接性。這點間接性來源於std::stringstream和std::string間的關聯。一般狀況下,一個std::stringstream對象老是會與其"附着"的std::string對象發生聯繫。或者其是從一個string對象(上例中的iss("12 14.1f"))構造而來以使用,或者其必須轉化爲一個string對象(上例中的oss.str())而使用。而新手常會會直覺地寫出string a << 12 << " is int";這樣的錯誤代碼。
  2. 格式化輸出的不便利性。相比於sprintf,std::stringstream是一個流對象,意味着其也有了更高的學習代價。簡單的sprintf,只須要翻查escape character的手冊,就能漂亮地進行格式化的輸出。而使用流進行格式化輸出的話,則須要控制一個狀態機。不少時候,程序員須要關心上一狀態對現有輸出的影響。並且一般也意味着須要輸入更多的代碼。不少時候程序員都會以爲很是麻煩。因此即便sprintf在C++代碼中缺失了類型匹配、異常處理、內存管理等等,程序員依然義無反顧地使用了它。(關於這一點,boost::format可能給出了一種跨平臺的中間的解決方案) 從以上兩個方面看,使用std::stringstream完成atoi/itoa雖然是更爲C++風格地、功能完備方式,但因爲學習代價的增高以及格式化輸出中的不便利性,其在實際場景中的應用也大大受限。

到了C++11中,標準委員會多是注意到這種"簡單比完備"更重要的狀況,因而在C++11中,標準增長了全局函數std::to_string,以及std::stoi/stol/stoll等等函數。(最初的paper稱之爲simple numeric access,N1982)其用法很是簡單:

string s; s += to_string(12) + " is int, "; s += to_string(3.14f) + " is float."; cout << s << endl;

這裏的to_string會根據參數的類型完成相應類型地轉換。而:

string s("12"); int i = stoi(s); cout << i << endl;

這樣的代碼則能夠順利完成atoi的任務。因爲其是C++11引入的函數,因此具有C所不具有的全部的C++庫代碼特徵:根據類型的處理,拋出異常,以及自動內存管理。

能夠看到,std::to_string在實際使用中可能會涉及一些字符串的連結。如咱們在文章一開始提到的,C++98中字符串連結一直是C++語言被詬病性能低於C的一個重要方面。而這在C++11引入了右值引用後獲得了很大的緩解。所以此時std::to_string這樣的函數的實用性就大大加強了。不過std::to_string並非itoa的一種終極方式。以浮點數爲例,to_string甚至連浮點數小數位顯示控制這樣基本的控制功能都不具有,所以其最大地特色仍是突出在其易用性上。C++程序員沒必要定義一個std::stringstream對象就能夠完成安全有效且沒必要關心任何內存的itoa工做。 而std::stoi/stol/stoll...系列更是簡單到只能完成一個數值的轉換,比起老是返回std::stringstream &的operator >>比起來功能性就差很遠了。後者能在一行代碼中轉化出多個數值。但前者最大地特色仍然突出在易用性上,沒必要"附着"一個std::stringstream類型。這對不少無需複雜atoi的程序而言也就足夠了。

相關文章
相關標籤/搜索