這個問題想說清楚還是挺複雜的,題主可以參考 New Options for Managing Character Sets in the Microsoft C/C++ Compiler 這篇文章。具體來說,原始碼檔案中的字串常數能否正確的顯示在控制台視窗中與以下幾個因素有關:
原始碼檔案(.cpp)保存時所使用的字元集(下文以 C1 取代)
編譯器在讀取原始碼檔案時所使用的內部字元集(source character set)(下文以 C2 取代)
編譯器在生編譯時所使用的字元集(execution character set)(下文以 C3 取代)
執行可執行程式的控制台視窗所使用的字元集(下文以 C4 取代)
第一,「原始碼檔案保存時所使用的字元集」決定了原始碼檔案中的抽象字元儲存到硬碟中是什麼樣的位元組。這裡所說的抽象字符,就是指人所能辨識的文字(如「張」)。而位元組則是由抽象字符按照字符集的規定映射的,不同的字符集映射的結果不一樣(如“張”在UTF-8 下映射的字節是E5 BC A0三個字節,而在GBK 下映射的位元組是D5 C5兩個位元組)。例如下面一行程式碼:
char *s = "张三";
其中的字串常數"张三"在使用不同的字元集(C1)保存時,映射的位元組是不同的。
第二,「編譯器在讀取原始碼檔案時所使用的內部字元集」決定了編譯器如何將讀入的原始碼檔案位元組流進行轉換。我所謂的轉換就是指把位元組流從一種編碼轉到另一種編碼。舉例來說,對於抽象字元“張”,如果採用 GBK 編碼,映射的位元組流就是D5 C5,而轉換到 UTF-8 編碼,就是把這個位元組流轉換成E5 BC A0。
這個字元集(C2)是由編譯器決定的,標準中並未規定。不同的編譯器可能採用不同的內部字元集,同樣的編譯器不同版本也可能採用不同的內部字元集。在Visual C++ 的某些版本中,內部字元集是UTF-8,編譯器會嘗試判斷原始檔案所使用的字元集(C1),並將其轉換成內部字元集(C2),如果編譯器無法判斷檔案所使用的字元集,則會預設其(C1)為目前作業系統的程式碼頁(default code page)(這裡如果判斷錯誤,就會造成亂碼或編譯出錯)。
例如:原始檔案如果是UTF-8 with BOM,則能夠正確的被Visual C++ 識別,其中的抽象字元「張」映射的位元組流E5 BC A0就會被正確的轉換成E5 BC A0(沒變)。而原始檔如果是UTF-8 without BOM,則不能正確的被Visual C++ 識別,編譯器會採用目前的程式碼頁來進行轉換,其中的抽象字元「張」所映射的位元組流E5 BC A0就會被當作GBK 編碼,錯誤的轉換成其它位元組流E5 AF AE ...(寮)。
第三,「編譯器在編譯時所使用的字元集」決定了編譯器如何把原始碼中使用內部字元集(C2)編碼的字元/字串常數轉還到編譯時所使用的字元集(C3)。這個字元集(C3)也是由編譯器決定的,標準中並未規定。 C++ 中的字元常數和字串常數有不同的類型,它們對應於不同的 C3。
在 Visual C++ 中,參考 String and Character Literals 和前文提到的博客,可以推知不同類型的字元/字串常數對應的 C3:
// Character literals
auto c0 = 'A'; // char, encoded as default code page
auto c1 = u8'A'; // char, encoded as UTF-8
auto c2 = L'A'; // wchar_t, encoded as UTF-16LE
auto c3 = u'A'; // char16_t, encoded as UTF-16LE
auto c4 = U'A'; // char32_t, encoded as UTF-32LE
// String literals
auto s0 = "hello"; // const char*, encoded as default code page
auto s1 = u8"hello"; // const char*, encoded as UTF-8
auto s2 = L"hello"; // const wchar_t*, encoded as UTF-16LE
auto s3 = u"hello"; // const char16_t*, encoded as UTF-16LE
auto s4 = U"hello"; // const char32_t*, encoded as UTF-32LE
編譯器根據字串常數的型別把其從 C2 轉換到 C3(前面如果判斷錯誤,這裡就會繼續保留錯誤)。
例如:auto s1 = "张";,抽象字元「張」在C2 中(UTF-8)映射的位元組流E5 BC A0就會轉換成在C3 中(CP936,GBK)映射的位元組流D5 C5。
auto s2 = u8"张";,抽象字元「張」在C2 中(UTF-8)映射的位元組流E5 BC A0就會被轉換成在C3 中(UTF-8)映射的位元組流E5 BC A0(不變)。
這個問題想說清楚還是挺複雜的,題主可以參考 New Options for Managing Character Sets in the Microsoft C/C++ Compiler 這篇文章。具體來說,原始碼檔案中的字串常數能否正確的顯示在控制台視窗中與以下幾個因素有關:
原始碼檔案(.cpp)保存時所使用的字元集(下文以 C1 取代)
編譯器在讀取原始碼檔案時所使用的內部字元集(source character set)(下文以 C2 取代)
編譯器在生編譯時所使用的字元集(execution character set)(下文以 C3 取代)
執行可執行程式的控制台視窗所使用的字元集(下文以 C4 取代)
第一,「原始碼檔案保存時所使用的字元集」決定了原始碼檔案中的抽象字元儲存到硬碟中是什麼樣的位元組。這裡所說的抽象字符,就是指人所能辨識的文字(如「張」)。而位元組則是由抽象字符按照字符集的規定映射的,不同的字符集映射的結果不一樣(如“張”在UTF-8 下映射的字節是
E5 BC A0
三個字節,而在GBK 下映射的位元組是D5 C5
兩個位元組)。例如下面一行程式碼:其中的字串常數
"张三"
在使用不同的字元集(C1)保存時,映射的位元組是不同的。第二,「編譯器在讀取原始碼檔案時所使用的內部字元集」決定了編譯器如何將讀入的原始碼檔案位元組流進行轉換。我所謂的轉換就是指把位元組流從一種編碼轉到另一種編碼。舉例來說,對於抽象字元“張”,如果採用 GBK 編碼,映射的位元組流就是
D5 C5
,而轉換到 UTF-8 編碼,就是把這個位元組流轉換成E5 BC A0
。這個字元集(C2)是由編譯器決定的,標準中並未規定。不同的編譯器可能採用不同的內部字元集,同樣的編譯器不同版本也可能採用不同的內部字元集。在Visual C++ 的某些版本中,內部字元集是UTF-8,編譯器會嘗試判斷原始檔案所使用的字元集(C1),並將其轉換成內部字元集(C2),如果編譯器無法判斷檔案所使用的字元集,則會預設其(C1)為目前作業系統的程式碼頁(default code page)(這裡如果判斷錯誤,就會造成亂碼或編譯出錯)。
例如:原始檔案如果是UTF-8 with BOM,則能夠正確的被Visual C++ 識別,其中的抽象字元「張」映射的位元組流
E5 BC A0
就會被正確的轉換成E5 BC A0
(沒變)。而原始檔如果是UTF-8 without BOM,則不能正確的被Visual C++ 識別,編譯器會採用目前的程式碼頁來進行轉換,其中的抽象字元「張」所映射的位元組流E5 BC A0
就會被當作GBK 編碼,錯誤的轉換成其它位元組流E5 AF AE ...
(寮)。第三,「編譯器在編譯時所使用的字元集」決定了編譯器如何把原始碼中使用內部字元集(C2)編碼的字元/字串常數轉還到編譯時所使用的字元集(C3)。這個字元集(C3)也是由編譯器決定的,標準中並未規定。 C++ 中的字元常數和字串常數有不同的類型,它們對應於不同的 C3。
在 Visual C++ 中,參考 String and Character Literals 和前文提到的博客,可以推知不同類型的字元/字串常數對應的 C3:
編譯器根據字串常數的型別把其從 C2 轉換到 C3(前面如果判斷錯誤,這裡就會繼續保留錯誤)。
例如:
auto s1 = "张";
,抽象字元「張」在C2 中(UTF-8)映射的位元組流E5 BC A0
就會轉換成在C3 中(CP936,GBK)映射的位元組流D5 C5
。auto s2 = u8"张";
,抽象字元「張」在C2 中(UTF-8)映射的位元組流E5 BC A0
就會被轉換成在C3 中(UTF-8)映射的位元組流E5 BC A0
(不變)。第四,「執行執行程式的控制台視窗所使用的字元集」決定如何把編譯好的執行程式中的位元組流轉換成抽象字元顯示在控制台中。例如,在上一步驟中的
s1
映射的位元組流就會透過 C4(CP 936)映射回抽象字元“張”,在我們看來就是正確的。而上一步中的s2
映射的位元組流就會透過 C4(CP 936)映射回抽像字元“寮”,在我們看來就是亂碼。以上,就是我理解的 C++ 中字元/字串的編碼處理方式,如果有誤還請指出:-)
題主可以試著在 Visual C++ 中把以下程式碼分別儲存成 CP936、UTF-8 with BOM、UTF-8 without BOM 的格式,看看輸出結果是什麼。
輸出的三個檔案中,前兩個檔案
s1.txt
和s2.txt
都能夠被正常的文字編輯器猜出其編碼格式,從而正確顯示內容,但是第三個檔案s3.txt
會顯示成部分或全部亂碼,因為其中既包含了UTF-8 編碼的字節流又包含了GBK 編碼的字節流,所以文本編輯器就不知道該用什麼編碼來把字節流映射回抽象文本了。Character Encoding Model
標準引用與參考文件
字符集
字串和字元文字
在 Microsoft C/C++ 編譯器中管理字元集的新選項