如何比較字符(char
)或字符串(char*
,std::string
,std::wstring
,等等),這是在不同的運行環境中可以安全地進行本地化和改變字符編碼?比較字符串/字符在本地化與相應的文字這樣的字符串/字符用C字面++安全的方式
讓我們以下面的最小例子作爲開始。
using namespace std;
// Get runtime locale and apply it to i/o streams
locale loc("");
cout.imbue(loc);
cin.imbue(loc);
// Ask question and compare answer
char c = '\0';
do {
cout << "Important question [y/n]" << endl;
cin >> c;
} while(c != 'n' && c != 'y');
if(c == 'n') {
// execute 'no'-branch
} else {
// execute 'yes'-branch
}
(我知道的例子可以在許多方面得到改善。輸入流應該讀取下一個字符之前被清除等。但在這裏,這是不是問題的關鍵。)
我的問題是將來自環境的字符c
與硬編碼字面值'n'
進行比較,儘管變量類型的名稱爲char
但我們實際上並未比較字符(或字符),而是按比特級別比較單個字節。
在編譯期間,文字'n'
被轉換爲執行字符集。如果編譯器在Linux下是gcc,則默認爲UTF-8。但是這不能保證,因爲標準只需要一個覆蓋特定字符的代碼集。所以實際上每個編譯器都可以自由選擇合適的字符集。但無論如何,假設'n'
被編譯器翻譯爲'\x6e'
片刻。
但是,運行時環境可以使用完全不同的編碼。假設環境使用UTF-16。如果用戶鍵入「n」,則輸入流將填充兩個字節序列"\x00\x6e"
。其中,cin >> c
讀取第一個字節'\x00'
並將其與'\x6e'
進行比較。顯然,這不是打算的。
此外,如果我們想要將字符串拆分爲標記,情況會變得更糟。有幾個功能(C的strtok
,boost::tokenize
),但基本上他們都做什麼strtok
。他們採用一個輸入字符串和一串字符作爲分隔符號。但是,這些函數不能在字符上工作,而是在字節上工作。
讓我們這個簡單的例子
strtok("alice, bob; charlie", ",;");
意向的第一個字符串應該被拆分無論是「」或‘’。此外,讓我們假設通過一些未知的奇蹟,兩個字符串幸運地由相同的字符編碼UTF-16編碼。雖然兩個字符串具有相同的編碼,但結果是總損失,因爲",;"
是四字節序列"\x00\x2c\x00\x3b"
,第一個字符串是40字節序列,每個第二個字節爲'\x00'
。因爲strtok
(和boost::tokenize
和其他)在字節上工作,結果是僞造的。
我知道還有std::wstring
,因爲C++ 11還有std::u16string
和std::u32string
,但它們並不是真正的救援。 (我不想詳細說明它們,因爲這個問題已經足夠長了。)
當然,像IBM的ICU或像Qt這樣的完整框架等第三方庫可以避免所有這些問題,但所有這些庫都解決了這些問題通過定義自己的字符串類來解決問題。
一方面,這些庫大多互相不兼容,或者如果想要合併這些庫,必須進行大量的類型轉換和字符串複製。另一方面,我通常只寫小命令行工具,我不想創建一個像Qt這樣的真正龐大的庫的依賴,只是爲了完成像這個問題的第一個例子那樣的任務。
我不能相信,像一個字符與'y'
或'n'
比較這樣一個瑣碎的問題沒有「標準」的解決方案,只使用C++標準庫。所以回到我原來的問題:
如何比較字符(char
)或字符串(char*
,std::string
,std::wstring
,等)與相應的文字,使得它是安全的定位和不同的字符編碼中不同的運行時間環境對其他庫的依賴性很小?
「我不能相信,對於比較字符'y'或'n'這樣的小問題,沒有」標準「的解決方案,只使用C++標準庫。」 - 不幸的是,情況正是如此。 – dgel