在 C++ 裡面,其實已經有不少在字串與數字間轉換的方法了,像是 sprintf、sscanf、atol、strtol、stringstream、to_string 等等…
而在 Boost C++ Libraries 裡面,為了各種原因,也有另外提供 lexical_cast、Convert 等函式庫,讓使用者可以更方便地進行文字和數字間的轉換。
在 C++17 裡面,又有另一個標準的選擇了!那就是
(參考)。std::from_chars()
為什麼在已經有這麼多方法的情況下,還要再多弄一個出來呢?基本上,
是一個相對低接、可以提供最好的效能的函式;或許使用上沒那麼便利,但是適用於非常在乎效能的時候。std::from_chars()
為了效能,在設計上:
- 他不會丟出例外
- 有記憶體空間的配置
- 也不支援區域設定(locale)
- 會回報轉換的錯誤
他新的 header file 是 <charconv> 這個新的檔案,裡面只有 from_chars() 和 to_chars() 這兩個函式。
以 from_chars() 來說,針對整數型別,他的介面是:
std::from_chars_result from_chars( const char* first, const char* last, TYPE &value, int base = 10);
其中,字串是把開始的位置(first)和結束的位置(last),作為參數傳入。
輸出則是以參考的形式傳入(value),讓函式修改他的值。
最後,則是可以指定要轉換成幾進位,數字可以從 2 – 36(應該是考慮到英文字母的數量)。
而除了整數型別的版本外,還有浮點數的版本,這部分支援 float、double、long double 這三種型別,其介面如下:
std::from_chars_result from_chars( const char* first, const char* last, float& value, std::chars_format fmt = std::chars_format::general);
前面的參數都一樣,只是最後一個參數,變成是 std::chars_format,用來指定輸入字串的格式;除了一般形式的 std::chars_format::fixed 外,他也支援科學符號的 std::chars_format::scientific;而如果兩者都有可能的話,也可以使用預設的 std::chars_format::general。
另外,他也支援十六進位的 std::chars_format::hex。
它的使用狀況,大致上會是:
#include <iostream> #include <charconv> #include <array> int main() { std::array<char, 10> str{ "42" }; int result; std::from_chars(str.data(), str.data() + str.size(), result); std::cout << result; }
而 str 也可以改用 std::string 等格式來做處理。
另外,這個函式也會回傳一個 std::from_cahrs_result,讓使用者可以確認轉換的結果是否有問題。(可能的錯誤有 std::errc::invalid_argument 和 std::errc::result_out_of_range)
而如果要反向,把數字轉換成字串的話,則是可以使用新的 to_chars() 這個函式(參考)。
不過由於他不負責記憶體的配置,所以在輸出的字串的部分,必須要先自行配置好記憶體空間;而他輸出的字串也不是 NULL-terminated 的格式,所以如果是要拿來當作一般的字串用,會需要自己加上 NULL,或轉換成其他的格式。
在整數型別的部分,他的介面是:
std::to_chars_result to_chars( char* first, char* last, TYPE &value, int base = 10);
參數的部分基本上和 from_chars() 是類似的,只是前面的輸入、輸出是反過來的。
而浮點數的部分,介面則是:
std::to_chars_result to_chars( char* first, char* last, float value, std::chars_format fmt, int precision);
格式上,一樣是有 float、double、long double 三種版本。
其中,最後兩個參數、格式(fmt)和精度(precision)都是可以省略的。
下面就是一個範例:
#include <iostream> #include <charconv> #include <system_error> #include <string_view> #include <array> int main() { std::array<char, 10> str; if (auto[p, ec] = std::to_chars(str.data(), str.data() + str.size(), 42); ec == std::errc()) std::cout << std::string_view(str.data(), p - str.data()); }
這邊可以看到,他是用一個 std::array<> 來儲存輸出的結果;而為了可以透過 cout 輸出,這邊是把她轉換成 std::string_view(參考)來輸出。
比較特別的,是這邊為了要知道他到底輸出到哪裡,所以一定要接他回傳的 to_chars_result;在它裡面,除了 std::errc 外,還有一個 char* 是用來記錄最後的位置,所以上面的 p – str.data() 基本上就是字串的長度了。
而如果要轉成一般的字串,也可以寫成
std::string s(str.data(), p);
這樣的形式。
而如果給的字串長度不夠的話,他也會回傳 std::errc::value_too_large 來讓使用者知道。
和 to_chars() 的簡單使用大概就是這樣了。std::from_chars()
基本上,這兩個函式因為要自己去計算字串的大小、配置記憶體空間,在使用上其實不算很方便,以把數字轉字串來說,可以直接輸出 std::string 的 to_string() 其實更為方便。
而如果要講格式化輸出的話,Boost 的 lexical_cast 或 Convert,都有更多的功能和彈性。
不過,這兩種新的函式,實際上最大的重點還是他們的效能。根據《How to Use The Newest C++ String Conversion Routines – std::from_chars》的說法:
- 在 gcc 中,他的效能是 stoi 的 4.5 倍、atoi 的 2.2 倍、istringsteam 的 50 倍
- 在 clang 中,他的效能是 stoi 的 3.5 倍、atoi 的 2.7 倍、istringsteam 的 60 倍
- 在 MSVC 中,他的效能是 stoi 的 3 倍、atoi 的 2 倍、istringsteam 的 50 倍
可以看到,效能的差異其實不算小。所以如果是要頻繁、大量地轉換的話,多花一些功夫,改用這系列的函式來做,應該是可以減少不少分析時間的~
所以,以個人來說,這兩組新的函式,應該算是比較適合寫在更底層的東西了。
附註:目前有完整支援的編譯器似乎還只有 Visual Studio 2017 15.9,GCC 8.0 和 CLang 7.0 都還只有整數、不支援其他型別。