撥開字符編碼迷霧系列文章連接:html
使用Visual Studio建立的C++工程能夠在工程屬性配置屬性-->常規
中配置字符集:使用Unicode字符集
(默認)、使用多字節字符集
。
如圖:
git
但這個設置項不會對編譯器處理字符編碼產生直接的影響(注意這裏的「直接」二字,第3節會說到),只會在工程屬性配置屬性-->C/C++-->預處理器
加入相應的宏:github
使用Unicode字符集 --> _UNICODE和UNICODE宏 使用多字節字符集 --> _MBCS宏
這幾個宏通常用來判斷是使用char仍是wchar_t,在系統API中使用比較多,如MessegeBox經過是否認義了UNICODE宏來決定是使用LPCSTR仍是LPCWSTR(LPCSTR即const char, LPCWSTR即const wchar_t):數據庫
#ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif // !UNICODE
上面提到了,定義API時經過判斷UNICODE宏是否認義來決定是使用char仍是wchar_t,那麼char和wchar_t有什麼不一樣了?編程
char和wchar_t是標準C/C++字符類型,並非windows特有的。 char固定佔1個字節,wchar_t固定佔2個字節,從內存的角度來看,char、wchar_t和其餘數據類型同樣,只是表明一段內存塊,用來存儲固定長度的二進制0或1。 在編程時,咱們通常習慣於將字符串儲到char或wchar_t定義的內存空間中,將整形存儲在int定義的內存空間中。windows
因此,用char仍是wchar_t來存儲字符,只是內存分配和數據存儲上面的事情,它們自己也是與字符編碼無直接關係的( 一樣注意這裏的「直接」二字,第3節會說到)。函數
VC++編譯器編譯源代碼的步驟中,涉及編碼處理的步驟主要有2個:
第1步:預處理
1.1) 讀取源文件,判斷源文件採用的字符編碼類型。(這一步不會改變文件內容)visual-studio
編譯器判斷源文件編碼類型的步驟爲: 1. 若文件開始處有BOM(EF BB BF),則斷定爲UTF-8編碼; 2. 若沒有BOM,則試圖從文件的前8個字節來判斷文件是否像UTF-16編碼,若是像,則就判斷爲UTF-16編碼。 3. 若是既沒BOM,也不是UTF-16編碼,則使用系統當前的代碼頁(簡體中文操做系統爲CP936)。
不瞭解字符編碼的朋友能夠參考前一篇博客撥開字符編碼的迷霧--字符編碼概述測試
1.2) 將源文件內容轉成源字符集
(Source Character Set),默認爲UTF-8編碼。編碼
第2步:連接
2.1) 將1.2中獲得的UTF-8轉爲執行字符集
(Execution Character Set):
L
標記的串,如L"abc"
, L'中'
),執行字符集
爲UTF-16編碼。L
標記的串),執行字符集
爲系統當前的代碼頁。如今咱們就能夠說清楚Visual Studio字符集設置、char、wchar_t是如何間接影響到編譯器對字符編碼的處理了:
Visual Studio字符集設置 | 決定聲明哪個宏(UNICODE仍是_MBCS宏) | 宏又決定了API參數使用char仍是wchar_t | 編譯器在進行【執行字符集】編碼時對char和wchar_採用不一樣的處理方式,從而對字符編碼產生了影響。
在Visual Studio 2010(含)以後,支持使用
# pragma execution_character_set
來設置執行字符集。
GBK D6 D0 Unicode 2D 4E UTF-8 E4 B8 AD
DumpCharacterCode
用於按字節打印內存中的數據:void DumpCharacterCode(const char* pChar, int iSize) { for(int i = 0; i < iSize; i++) { char a = *pChar++; printf("%02X ", a & 0xff); } printf("\n"); }
設置系統代碼頁的方法:
「控制面板」 --> 「區域和語言」 --> 「管理」 --> 「非Unicode程序的語言」 --> 「更改系統區域設置」
Visual Studio保存文件到指定編碼方法:
「文件」 --> 「高級保存選項」
測試代碼以下:
int _tmain(int argc, _TCHAR* argv[]) { char buf[100] = {"中"}; // char DumpCharacterCode(buf, 2); // 也能夠打印4個字節 return 0; }
針對不一樣的系統代碼頁和源文件編碼,打印出的漢字「中」的編碼分別爲:
測試用例 | 系統代碼頁 | 保存源文件編碼 | 編譯器判斷文件採用的編碼 | 源字符集(Source Character Set) | 執行字符集(Execution Character Set) | 打印輸出 |
---|---|---|---|---|---|---|
用例1 | 簡體中文 CP936 | 簡體中文 CP936 | 簡體中文 CP936 | UTF-8 | 簡體中文 CP936 | D6 D0 |
用例2 | 簡體中文 CP936 | UTF-8 BOM | UTF-8 | UTF-8 | 簡體中文 CP936 | D6 D0 |
用例3 | 簡體中文 CP936 | UTF-8 | 簡體中文 CP936 | UTF-8 | 簡體中文 CP936 | 編譯錯誤(C2146) |
用例4 | 西歐 CP1252 | 簡體中文 CP936 | 西歐 CP1252 | UTF-8 | 西歐 CP1252 | D6 D0 |
用例5 | 西歐 CP1252 | UTF-8 BOM | UTF-8 BOM | UTF-8 | 西歐 CP1252 | 3F 00 |
表格中列4~6依次對應編譯處理源文件的幾個步驟。
3F
對應的ASCII字符爲?
,編譯器遇到不能識別的字符時,就會用?
來替代。 出現?
的狀況會伴隨着編譯警告C4566
。
上面出現了1次3F
(用例5),致使亂碼的緣由是UTF-8 --> 西歐 CP1252
. 西歐 CP1252
也就是ASCII的擴展,不支持漢字,因此用3F
替代。
微軟的編譯器只能識別帶BOM的UTF-8,用例3的UTF-8沒帶BOM,編譯器會斷定源文件編碼爲系統當前代碼頁CP936。「中」的UTF-8編碼爲E4 B8 AD
,列5執行從CP936到UTF-8轉換以後變成了E6 B6 93 3F
,列6再要將E6 B6 93 3F
轉換爲CP936確定是轉換不回去的,至關於 UTF-8(1) --> UTF-8 (2),再將UTF-8(2)轉換回CP936,這時確定獲得的字符不是原來的字符了。
D6 D0
,而不是3F
?對着用例4的各個順序來看,源文件經過CP936保存着,但編譯器經過CP1252來讀取的,CP1252就是ASCII擴展,單字節的,雖然此時顯示爲亂碼,但各字節仍然是D6 D0;而後將讀取到的文件內容從CP1252轉成UTF-8編碼,轉碼後爲C3 96 C3 90;而後再將UTF-8編碼轉回爲CP1251,轉碼就又變成了D6 D0。 但這個D6 D0
在CP1252中是沒法顯示的,若是咱們在用例4加入MessageBoxA(NULL, "中", "test", MB_OK);
會發現彈出的對話框中顯示仍然是亂碼。
可使用下面的代碼進行測試(ANSIToUTF八、UTF8ToANSI函數見撥開字符編碼的迷霧--字符編碼轉換):
int _tmain(int argc, _TCHAR* argv[]) { char buf[3] = { 0 }; // 模擬CP936編碼的「中」 buf[0] = 0xD6; buf[1] = 0xD0; std::string strUTF8 = ANSIToUTF8(buf, 1252); char *p = (char*)strUTF8.c_str(); // 經過visual studio查看指針p處內存爲: C3 96 C3 90 std::string str = UTF8ToANSI(strUTF8, 1252); p = (char*)str.c_str(); // 經過visual studio查看指針p處內存爲: D6 D0 return 0; }
測試代碼以下:
int _tmain(int argc, _TCHAR* argv[]) { wchar_t buf[100] = {L"中"}; // wchar_t DumpCharacterCode((char*)buf, 4); // 打印4個字節 return 0; }
一樣,針對不一樣的系統代碼頁和源文件編碼,打印出的漢字「中」的編碼分別爲:
測試用例 | 系統代碼頁 | 保存源文件編碼 | 編譯器判斷文件採用的編碼 | 源字符集(Source Character Set) | 執行字符集(Execution Character Set) | 打印輸出 |
---|---|---|---|---|---|---|
用例1 | 簡體中文 CP936 | 簡體中文 CP936 | 簡體中文 CP936 | UTF-8 | UTF-16 | 2D 4E 00 00 |
用例2 | 簡體中文 CP936 | UTF-8 BOM | UTF-8 | UTF-8 | UTF-16 | 2D 4E 00 00 |
用例3 | 簡體中文 CP936 | UTF-8 | 簡體中文 CP936 | UTF-8 | UTF-16 | 編譯錯誤(C2146) |
用例4 | 西歐 CP1252 | 簡體中文 CP936 | 西歐 CP1252 | UTF-8 | UTF-16 | D6 00 D0 00 大小端 |
用例5 | 西歐 CP1252 | UTF-8 BOM | UTF-8 BOM | UTF-8 | UTF-16 | 2D 4E 00 00 |
經過第3節的說明,很容易知道,要開發支持多語言,在任意語言(系統代碼頁)的windows環境下都正常編譯,且運行起來沒有亂碼的程序,須要遵循以下原則:
作到上面3步,你的代碼被別人從github上clone下來編譯,不會由於你代碼中含有中文等字符,產生相似error C2015
這樣的編譯錯誤,更不會產生亂碼。
本文介紹的方法只用來解決硬編碼字符亂碼的問題,至於數據傳輸中的亂碼,須要統一字符編碼來解決。