C++:在Windows上使用Unicode的正確姿勢

1. 需要關(guān)注的字符編碼

在處理程序的字符編碼問題時,應(yīng)該關(guān)注3個階段的編碼設(shè)置。

  • 源代碼的編碼
    源代碼本身就是文本文件复唤。在編譯時,編譯器需要知道源代碼的編碼格式烛卧,如果編碼錯誤佛纫,將無法通過編譯。
  • 運行時的編碼
    程序在處理字符時总放,需要確定字符用什么編碼在內(nèi)存中表示呈宇。
  • 終端的編碼
    終端在向程序輸入字符時,需要一種編碼來將用戶的輸入轉(zhuǎn)換成二進(jìn)制局雄。反之甥啄,在接受程序的輸出時,也需要將二進(jìn)制反編碼成字符炬搭。
    接下來將詳細(xì)說明關(guān)于這三項編碼的設(shè)置蜈漓。

2. 使用UTF-8編寫源代碼

2.1 Visual Studio 的默認(rèn)編碼

Visual Studio 的源代碼保存格式默認(rèn)為ANSI,即“服從系統(tǒng)區(qū)域設(shè)置”尚蝌。中文版Windows的ANSI一般為GB系列編碼迎变,英文版的則是ASCII碼,不同地區(qū)皆有不同飘言。以ANSI保存的文件受系統(tǒng)區(qū)域設(shè)置影響衣形,拷貝到不同區(qū)域設(shè)置的電腦上可能無法打開。

幸運的是,Unicode解決了這個問題谆吴。使用Unicode的文件在任何電腦上都能打開倒源。Unicode有不同實現(xiàn)版本,這里我們將采用最常見的UTF-8格式(關(guān)于Unicode和UTF-8的關(guān)系和其他細(xì)節(jié)句狼,本文不再贅述)笋熬。UTF-8分為帶BOM和不帶BOM的版本。在Windows上最簡單的方式是將源代碼保存為帶BOM的版本腻菇,這樣MSVC編譯器能自動識別胳螟。當(dāng)然,如果你是純粹UTF-8的信仰者筹吐,可以在編譯時加上/utf-8(方法看這里)來使用無BOM的UTF-8糖耸。

2.2 設(shè)置保存編碼

設(shè)置方式非常簡單,在Viusal Studio中丘薛,進(jìn)入文件→高級保存選項嘉竟。修改編碼即可。
高級保存選項

如果沒有高級保存選項洋侨,可以打開 工具→自定義→命令舍扰,選中菜單欄按鈕,右邊改成文件希坚。
添加命令

點擊“添加命令”边苹,在“文件”類別中找到“高級保存選項”命令,添加即可裁僧。

3. 終端編碼設(shè)置

在C++代碼調(diào)用system函數(shù)勾给,執(zhí)行CHCP指令。

 system("CHCP 65001");

如此锅知,終端的編碼即被設(shè)為UTF-8。這條代碼執(zhí)行后脓钾,終端中如果出現(xiàn)“Active code page 65001”的提示售睹,則說明設(shè)置成功。

4. Unicode字符的運行時處理

4.1 UTF-8格式的字符串字面量

u8 開頭的字符串字面量在程序內(nèi)部以UTF-8格式表示可训。如

u8"你好Unicode!"

這樣的字符串要用 string 類型接收昌妹。

string hello = u8"你好Unicode!"

4.2 UTF-8格式的輸入/輸出流

如果你的終端已設(shè)置成UTF-8的格式,則可以用 string 類型接受由 std::cin 傳入的UTF-8字符握截,向 std::cout 輸出UTF-8字符飞崖。

string input;
std::cin >> input;
std::cout << u8"復(fù)讀機說:" << input << endl;

你可能聽說過還有一種針對 wstring 的輸出流 std::wcout,但個人不建議使用 wcout谨胞,即使是要輸出 wstring固歪。原因有兩點(wcin同理):

  • 根據(jù)cpp reference的說明,wcout 不能和 cout 直接混用。
  • 不同系統(tǒng)可能要為 wcout 綁定不同的 locale 信息牢裳,這樣十分麻煩逢防,且對可移植性有害。

如果要輸出 wstring蒲讯,建議先將其轉(zhuǎn)換為 string 再輸出到 cout 中忘朝。這并不麻煩,下面有演示判帮。

4.3 多字節(jié)字符串的切分

根據(jù)UTF-8格式的要求局嘁,一個字符用1-4個字節(jié)表示,反映到string中晦墙,就是1-4個char表示一個字符悦昵。這種字符串一般稱為多字節(jié)字符串。多字節(jié)字符串可以勝任各種對字符串的整體操作偎痛,比如拷貝旱捧、連接等。然而在需要遍歷字符踩麦,對字符一一操作時枚赡,就需要進(jìn)行切分。切分后的結(jié)果為wstring谓谦,即寬字符串 類型贫橙。wstring由wchar_t(寬字符)組成,其中每個 wchar_t 只表示一個字符反粥。通過遍歷wstring卢肃,即可對每個寬字符進(jìn)行操作。

C++ 標(biāo)準(zhǔn)庫中的codecvt頭文件提供了多字節(jié)字符串與寬字符串的轉(zhuǎn)換API才顿。他們是
wstring_convert 模板類中的兩個方法莫湘。

  • wstring from_bytes(string)
    將string(多字節(jié)字符串)切分為wstring(寬字符串)。
  • string to_bytes(wstring)string to_bytes(wchar_t)
    將wstring(寬字符串)或wchar_t(寬字符)轉(zhuǎn)為string(多字節(jié)字符串)郑气。

例如幅垮,將string轉(zhuǎn)為wstring可以用如下代碼。

#include <codecvt>
//......
using namespace std;
string input = u8"我是UTF-8字符串";
wstring_convert<codecvt_utf8<wchar_t>> converter;//一個wstring_convert對象可以多次使用
wstring output = converter.from_bytes(input);

然后尾组,我們可以遍歷wstring忙芒,將每個wchar_t轉(zhuǎn)換成string,再一一輸出至cout(不直接輸出至wcout的原因前文已敘述)讳侨。

for (size_t i = 0; i < output.length(); i++) 
    cout << converter.to_bytes(output[i]) << endl;

輸出如下

我
是
U
T
F
-
8
字
符
串

這是一個簡單的寬字符/多字節(jié)字符串互相操作的例子呵萨。總體來說跨跨,需要嚴(yán)格區(qū)分字符與字符之間的邊界的操作需要將string轉(zhuǎn)為wstring潮峦,其他情況下一般使用string即可解決問題。

參考

https://docs.microsoft.com/en-us/cpp/build/reference/execution-charset-set-execution-character-set?view=vs-2019

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市跑杭,隨后出現(xiàn)的幾起案子铆帽,更是在濱河造成了極大的恐慌,老刑警劉巖德谅,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爹橱,死亡現(xiàn)場離奇詭異,居然都是意外死亡窄做,警方通過查閱死者的電腦和手機愧驱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椭盏,“玉大人组砚,你說我怎么就攤上這事√图眨” “怎么了糟红?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乌叶。 經(jīng)常有香客問我盆偿,道長,這世上最難降的妖魔是什么准浴? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任事扭,我火速辦了婚禮,結(jié)果婚禮上乐横,老公的妹妹穿的比我還像新娘求橄。我一直安慰自己,他們只是感情好葡公,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布罐农。 她就那樣靜靜地躺著,像睡著了一般催什。 火紅的嫁衣襯著肌膚如雪啃匿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天蛆楞,我揣著相機與錄音,去河邊找鬼夹厌。 笑死豹爹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矛纹。 我是一名探鬼主播臂聋,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了孩等?” 一聲冷哼從身側(cè)響起艾君,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肄方,沒想到半個月后冰垄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡权她,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年虹茶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隅要。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝴罪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出步清,到底是詐尸還是另有隱情要门,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布廓啊,位于F島的核電站欢搜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崖瞭。R本人自食惡果不足惜狂巢,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望书聚。 院中可真熱鬧唧领,春花似錦、人聲如沸雌续。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驯杜。三九已至受啥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸽心,已是汗流浹背滚局。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留顽频,地道東北人藤肢。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像糯景,于是被迫代替她去往敵國和親嘁圈。 傳聞我的和親對象是個殘疾皇子省骂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容