本節對作業系統裡的字元編碼進行簡單介紹,這對於程式的跨平台開發、漢字亂碼問題解決等都是很重要的知識,了解這些知識就不再害怕遇到程式亂碼的問題了。
ANSI 多位元組編碼
在最早的時期,計算機只支援英文字元,那時都是用 ASCII(American Standard Code for Information Interchange,美國標準資訊交換程式碼)編碼,一個字母或符號只需要一個位元組儲存。隨著計算機的推廣應用,越來越多的國家和地區面臨本地語言文字如何在計算機裡使 用和顯示的問題。對於中文 DOS 系統和早期的中文 Windows 系統,大陸制定了國標碼 GB2312,台港澳地區則使用了大五碼 Big5。微軟針對這些在地化字元編碼採用的就是用 ANSI(American National Standards Institute,美國國家標準學會)多位元組編碼方式,系統裡的英文和符號就使用單位元組的 ASCII(0x00~0x7f),而對於漢字之類的在地化字元編碼,就採用 0x80~0xFF 範圍內的多個位元組來表示,這樣既能相容 ASCII ,又能正常使用在地化語言文字。
大陸的國標碼發展了好幾代,歸結如下:
中文字元編碼 |
說明 |
GB2312 |
1980 年發布,收錄了 7445 個字元,包括 6763 個漢字和 682 個其它符號。漢字是雙位元組編碼。 |
GBK |
1995 年發布,收錄了 21886 個符號,包括 21003 個漢字和 883 個其它符號。漢字是雙位元組編碼。簡體中文 Windows 目前預設採用這種在地化編碼。 |
GB18030-2000 |
2000 年發布,收錄了 27533 個漢字,漢字分為雙位元組編碼部分和四位元組編碼部分。 |
GB18030-2005 |
2005 年發布,收錄了 70244 個漢字,漢字也分為雙位元組編碼部分和四位元組編碼部分。 |
新的國標碼標準通常是相容舊的編碼方式的,所以一般對簡體中文的文字選擇 GBK 或 GB18030 編碼都是可以正常顯示的。微軟針對各種在地化語言的頁面有自己的編號方式,GBK 對應的內碼錶編號是 936,GB18030 對應的內碼錶編號是 54936,Big5 對應的內碼錶編號是 950。
關於漢字編碼可以參考:http://www.fmddlmyy.cn/text24.html
關於內碼錶編號可以參考:http://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%A1%B5
Unicode 系列編碼
ANSI 多位元組編碼解決了各種語言文字的在地化使用問題,也有它自己的缺陷:各地制定的編碼標準只對自己的語言文字有效,而不同語言文字的編碼都是衝突的,因為大家都用 0x80~0xFF 範圍位元組表示自己的語言文字,而不考慮別的語言文字如何編碼,衝突在所難免。比如簡體中文(GBK)的文字放到繁體中文(Big5)的作業系統裡,就被預設解析成繁體字編碼,兩種編碼是衝突的,就會顯示混亂的繁體字,反過來也一樣。
因此國際組織制定了 Unicode 編碼,也叫萬國碼、國際碼等,這種字元編碼是對全球語言統一分配編碼區間,各種語言字元互相不衝突,都可以相容使用。Unicode 編碼從最初的 1.0,慢慢發展到今年發布的 8.0,包含的語言文字越來越多。
Unicode 編碼系統,可分為編碼方式和實現方式兩個層次。對於國際組織發布的 Unicode 編碼標準,對應的就是編碼方式,最常用的是 UCS-2(Universal Character Set 2),採用兩位元組編碼一個字元。當然國際語言文字太多,兩位元組不夠用了,就有四位元組編碼方式 UCS-4。這個僅僅是標準,而不是實現,在編碼實現的過程中,有些考慮相容舊的單位元組 ASCII 編碼,有些不考慮相容性;有些考慮雙位元組中哪個位元組放在前面,哪個位元組放在後面的問題,即 BOM(Byte Order Mark,位元組順序標記)的作用。因此誕生了多種國際碼的實現方式,統稱為 Unicode 轉換格式(Unicode Transformation Format,UTF):
Unicode 轉換格式 |
說明 |
UTF-8 |
靈活的變長編碼,對於 ASCII 使用一個位元組編碼,其他在地化語言文字用多個位元組編碼,最長可以到 6 個位元組編碼一個字元。對於漢字,通常是 3 個位元組表示一個漢字。這是 Unix/Linux 系統預設的字元編碼。 |
UTF-16 |
相容 UCS-2,一般都是兩位元組表示一個字元,對於超出兩位元組的國際碼字元,使用一對兩位元組來表示。在儲存時,按兩個位元組的排布順 序,可以分為 UTF-16LE(Little Endian,小端位元組序)和UTF-16BE(Big Endian,大端位元組序),微軟所說的 Unicode 預設就是 UTF-16LE。 |
UTF-32 |
同 UCS-4,因為用四個位元組表示一個字元,所以不需要考慮擴充套件了。這種編碼方式簡單,但也特別浪費空間,所以應用很少。在儲存時也分為 UTF-32BE 和 UTF-32LE,因為用得少,所以不用太關心這種編碼格式。 |
關於 Unicode 編碼格式參考:http://baike.baidu.com/view/40801.htm
關於 UTF 和 BOM 參考:http://www.unicode.org/faq/utf_bom.html
C++使用的字串
在 C++ 中,以前通常使用 char 表示單位元組的字元,使用 wchar_t 表示寬字元,對國際碼提供一定程度的支援。 char * 字串有專門的封裝類 std::string 來處理,標準輸入輸出流是 std::cin 和 std::cout 。對於 wchar_t * 字串,其封裝類是 std::wstring,標準輸入輸出流是 wcin 和 wcout。
雖然規定了寬字元,但是沒有明確一個寬字元是佔用幾個位元組,Windows 系統裡的寬字元是兩個位元組,就是 UTF-16;而 Unix/Linux 系統裡為了更全面的國際碼支援,其寬字元是四個位元組,即 UTF-32 編碼。這為程式的跨平台帶來一定的混亂,除了 Windows 程式開發常用 wchar_t* 字串表示 UTF-16 ,其他情況下 wchar_t* 都用得比較少。
MFC 一般用自家的 TCHAR 和 CString 類支援國際化,當沒有定義 _UNICODE 宏時,TCHAR = char,當定義了 _UNICODE宏 時,TCHAR = wchar_t,CString 內部也是類似的。Qt 則用 QChar 和 QString 類(內部恆定為 UTF-16),一般的圖形開發庫都用自家的字串類庫。
在新標準 C++11 中,對國際碼的支援做了明確的規定:
-
char * 對應 UTF-8 編碼字串(程式碼表示如 u8"多種文字"),封裝類為 std::string;
-
新增 char16_t * 對應 UTF-16 編碼字串(程式碼表示如 u"多種文字"),封裝類為 std::u16string ;
-
新增 char32_t * 對應 UTF-32 編碼字串(程式碼表示如 U"多種文字"),封裝類為 std::u32string 。
因為 Qt 有封裝好的 QString,所以不太需要這些新增的字串格式。關於 C++11 字串更詳細的內容參考:http://blog.poxiao.me/p/unicode-character-encoding-conversion-in-cpp11/
原始檔字元編碼測試
在 Windows 系統裡最常用的文字字元編碼格式是 ANSI (簡體是 GBK,繁體是 Big5)和 Unicode (UTF-16LE)格式,Windows 命令列預設的輸入輸出格式是 ANSI 的。在 Linux 系統裡統統都是 UTF-8 ,這個比較省心。
Windows 自帶的記事本,以及 Notepad++ 工具、Linux 系統裡的 kwrite、gedit 等都支援各種編碼格式的文字顯示。以 Windows 記事本為例,開啟任意一個文字檔案,選擇選單“檔案 --> 另存為”,點開“另存為”對話方塊最下面的“編碼”: