0 前言
在平時(shí)的開(kāi)發(fā)過(guò)程中大部分人應(yīng)該都遇到過(guò)中文亂碼問(wèn)題,瀏覽網(wǎng)頁(yè)時(shí)也會(huì)遇到內(nèi)容顯示亂碼的情況,一般遇到這種情況我們想到的可能是編碼問(wèn)題。那我們說(shuō)的編碼具體是指什么,亂碼問(wèn)題的根本原因是什么,又該如何解決呢?
答案的關(guān)鍵就是本文接下來(lái)要介紹的字符集與字符編碼。
1 概述
首先介紹一下字符,字節(jié),字符串,字符集和字符編碼等基本概念。
- 字符(Character): 各種文字和符號(hào)的總稱(chēng),包括各國(guó)家文字、標(biāo)點(diǎn)符號(hào)、圖形符號(hào)、數(shù)字等。
- 字節(jié)(Byte): 計(jì)算機(jī)信息技術(shù)用于計(jì)量存儲(chǔ)容量的一種計(jì)量單位,也表示一些計(jì)算機(jī)編程語(yǔ)言中的數(shù)據(jù)類(lèi)型和語(yǔ)言字符。
- 字符串(string): 一個(gè)連續(xù)的字符序列,在存儲(chǔ)上類(lèi)似于字符數(shù)組。
- 字符集(Character Set): 多個(gè)字符的集合,字符集種類(lèi)較多,每個(gè)字符集包含的字符個(gè)數(shù)不同。
- 字符編碼(Character encoding): 也稱(chēng)字集碼,是把字符集中的字符編碼為指定集合中某一對(duì)象(例如:比特模式、自然數(shù)序列、8位組或者電脈沖),以便文本在計(jì)算機(jī)中存儲(chǔ)和通過(guò)通信網(wǎng)絡(luò)的傳遞。
- 單字節(jié)字符集(Single Byte Character Set, SBCS): 所有字符都只用一個(gè)字節(jié)表示。用一個(gè)字節(jié)表示的0來(lái)標(biāo)志SBCS字符串的結(jié)束。
- 多字節(jié)字符集(Multi-Byte Character Set, MBCS):部分字符用一個(gè)字節(jié)表示,部分字符用兩個(gè)或更多字節(jié)表示。Windows中的MBCS包含兩種字符,單字節(jié)字符(Single-Byte Characters)和雙字節(jié)字符(Double-Byte Characters)。有一些特定的值被保留用來(lái)表明它們是雙字節(jié)字符的一部分。MBCS字符串也使用單字節(jié)的0來(lái)標(biāo)志字符串結(jié)束。
- Unicode字符集: 通常又稱(chēng)為寬字符集(Wide Character Set),所有字符都用兩個(gè)字節(jié)來(lái)表示。 注意,不要混淆Unicode字符集與MBCS,Unicode字符串采用兩個(gè)字節(jié)表示的0作為結(jié)束標(biāo)志。
常見(jiàn)的字符集有:ASCII字符集、GB2312字符集、GBK字符集、Big5字符集、GB18030字符集、Unicode字符集等。
一般情況下一個(gè)字符集對(duì)應(yīng)一種字符編碼,但是Unicode比較特殊,存在多種字符編碼標(biāo)準(zhǔn),比如:UTF-7,UTF-8,UTF-16,UTF-32等。
根據(jù)各個(gè)字符集的特性及發(fā)展歷程可以將其劃分成三類(lèi),如下圖所示:

上圖中只列舉了幾種常見(jiàn)的字符集與字符編碼,更多內(nèi)容請(qǐng)參閱字符編碼。
注意: 平時(shí)與人溝通的時(shí)候要弄清楚自己說(shuō)的是字符集還是字符編碼,尤其是在談?wù)揢nicode的時(shí)候。
2 ASCII
- ASCII(American Standard Code for Information Interchange,美國(guó)信息互換標(biāo)準(zhǔn)編碼)是基于羅馬字母表的一套電腦編碼系統(tǒng)。
- 包含了英文大小寫(xiě)字符、阿拉伯?dāng)?shù)字和西文符號(hào)等可顯示字符以及回車(chē)鍵、退格、換行鍵等控制字符。
- 主要用于顯示現(xiàn)代英語(yǔ)和其他西歐語(yǔ)言,是現(xiàn)今最通用的單字節(jié)編碼系統(tǒng),并等同于國(guó)際標(biāo)準(zhǔn)ISO 646。
- 基本字符集采用7位(bits)表示一個(gè)字符,共128個(gè)字符,字符值從0到127,其中32到126是可打印字符。
- 擴(kuò)展字符集采用8位(bits)表示一個(gè)字符,共256個(gè)字符,增加了表格符號(hào)、計(jì)算符號(hào)、希臘字母和特殊的拉丁符號(hào),可以表示更多的歐洲常用字符。
3 ANSI(GB2312, GBK, Big5, GB18030)
隨著計(jì)算機(jī)的不斷普及,原來(lái)的ASCII單字節(jié)編碼已經(jīng)無(wú)法滿(mǎn)足世界各地的字符表示要求,于是,各個(gè)國(guó)家和地區(qū)都設(shè)計(jì)了一系列滿(mǎn)足于本國(guó)和地區(qū)的字符集與字符編碼。
以中國(guó)為例,為了滿(mǎn)足國(guó)內(nèi)計(jì)算機(jī)使用漢字的需求,中國(guó)國(guó)家標(biāo)準(zhǔn)總局發(fā)布了一系列的漢字字符集國(guó)家標(biāo)準(zhǔn)編碼,統(tǒng)稱(chēng)為GB碼,或國(guó)標(biāo)碼。
3.1 GB2312
GB2312是一個(gè)簡(jiǎn)體中文字符集,采用了二維矩陣編碼法對(duì)所有字符進(jìn)行編碼:
- 首先構(gòu)造一個(gè)94行94列的方陣,對(duì)每一行稱(chēng)為一個(gè)“區(qū)”,每一列稱(chēng)為一個(gè)“位”,
- 然后將所有字符依照下表的規(guī)律填寫(xiě)到方陣中。
| 分區(qū)范圍 | 符號(hào)類(lèi)型 |
|---|---|
| 第01區(qū) | 中文標(biāo)點(diǎn)、數(shù)學(xué)符號(hào)以及一些特殊字符 |
| 第02區(qū) | 各種各樣的數(shù)學(xué)序號(hào) |
| 第03區(qū) | 全角西文字符 |
| 第04區(qū) | 日文平假名 |
| 第05區(qū) | 日文片假名 |
| 第06區(qū) | 希臘字母表 |
| 第07區(qū) | 俄文字母表 |
| 第08區(qū) | 中文拼音字母表 |
| 第09區(qū) | 制表符號(hào) |
| 第10-15區(qū) | 無(wú)字符 |
| 第16-55區(qū) | 一級(jí)漢字(以拼音字母排序) |
| 第56-87區(qū) | 二級(jí)漢字(以部首筆畫(huà)排序) |
| 第88-94區(qū) | 無(wú)字符 |
這樣所有的字符在方陣中都有一個(gè)唯一的位置,這個(gè)位置可以用區(qū)號(hào)、位號(hào)合成表示,稱(chēng)為字符的區(qū)位碼。
GB2312編碼采用兩個(gè)字節(jié)表示一個(gè)漢字,區(qū)碼和位碼分別占用一個(gè)字節(jié)。由于區(qū)碼和位碼的取值范圍都是在1-94之間,同西文的存儲(chǔ)表示沖突。為了與西文進(jìn)行區(qū)別,存儲(chǔ)時(shí)將區(qū)位碼的每個(gè)字節(jié)分別加上A0H(160)轉(zhuǎn)換為存儲(chǔ)碼。以漢字“啊”為例,區(qū)位碼為1601(1001H),存儲(chǔ)碼為B0A1H,轉(zhuǎn)換過(guò)程如下:
| 區(qū)位碼 | 區(qū)碼轉(zhuǎn)換 | 位碼轉(zhuǎn)換 | 存儲(chǔ)碼 |
|---|---|---|---|
| 1001H | 10H+A0H=B0H | 01H+A0H=A1H | B0A1H |
3.2 GBK
- GBK是GB2312的擴(kuò)展,K為擴(kuò)展的漢語(yǔ)拼音中“擴(kuò)”字的聲母。英文全稱(chēng)Chinese Internal Code Specification。
- 字符有一字節(jié)和雙字節(jié)編碼,00–7F范圍內(nèi)是第一個(gè)字節(jié),和ASCII保持一致,此范圍內(nèi)嚴(yán)格上說(shuō)有96個(gè)文字和32個(gè)控制符號(hào)。
- 之后的雙字節(jié)中,前一字節(jié)是雙字節(jié)的第一位。總體上說(shuō)第一字節(jié)的范圍是81–FE(也就是不含80和FF),第二字節(jié)的一部分領(lǐng)域在40–7E,其他領(lǐng)域在80–FE。編碼范圍如下所示:

- 雙字節(jié)符號(hào)可以表達(dá)的64K空間如下圖所示。綠色和黃色區(qū)域是GBK的編碼,紅色是用戶(hù)定義區(qū)域。沒(méi)有顏色區(qū)域是不正確的代碼組合。

3.3 Big5
- Big5又稱(chēng)為大五碼或五大碼,是一種繁體字編碼,主要在臺(tái)灣,香港和澳門(mén)等使用繁體字的地區(qū)使用。
- Big5采用雙字節(jié)表示一個(gè)字符,第一個(gè)字節(jié)稱(chēng)為“高位字節(jié)”,第二個(gè)字節(jié)稱(chēng)為“低位字節(jié)”。
- “高位字節(jié)”范圍0x81-0xFE,“低位字節(jié)”范圍0x40-0x7E,及0xA1-0xFE。具體分區(qū)如下所示:
| 分區(qū) | 備注 |
|---|---|
| 0x8140-0xA0FE | 保留給用戶(hù)自定義字符(造字區(qū)) |
| 0xA140-0xA3BF | 標(biāo)點(diǎn)符號(hào)、希臘字母及特殊符號(hào),包括在0xA259-0xA261,安放了九個(gè)計(jì)量用漢字:兙兛?jī)羶纼膬艈憝櫦H。 |
| 0xA3C0-0xA3FE | 預(yù)留。此區(qū)沒(méi)有開(kāi)放作造字區(qū)用。 |
| 0xA440-0xC67E | 常用漢字,先按筆劃再按部首排序。 |
| 0xC6A1-0xC8FE | 保留給用戶(hù)自定義字符(造字區(qū)) |
| 0xC940-0xF9D5 | 次常用漢字,亦是先按筆劃再按部首排序。 |
| 0xF9D6-0xFEFE | 保留給用戶(hù)自定義字符(造字區(qū)) |
3.4 GB18030
GB18030是我國(guó)目前最新的變長(zhǎng)多字節(jié)字符集,兼容GB2312,GBK以及Unicode3.1。主要特點(diǎn)如下:
- 采用變長(zhǎng)多字節(jié)編碼,每個(gè)字可以由1個(gè)、2個(gè)或4個(gè)字節(jié)組成。
- 編碼空間龐大,最多可定義161萬(wàn)個(gè)字符。
- 支持中國(guó)國(guó)內(nèi)少數(shù)民族文字,不需要?jiǎng)佑迷熳謪^(qū)。
- 漢字收錄范圍包含繁體漢字以及日韓漢字。
GB18030包含三種長(zhǎng)度的編碼:?jiǎn)巫止?jié)的ASCII、雙字節(jié)的GBK(略帶擴(kuò)展)、以及用于填補(bǔ)所有Unicode碼位的四字節(jié)UTF區(qū)段。編碼范圍如下圖所示:

3.5 Unicode
不同的國(guó)家和地區(qū)制定了適用于本國(guó)和地區(qū)的字符表示標(biāo)準(zhǔn),但是這些標(biāo)準(zhǔn)之間往往是不兼容的,比如用GB18030編碼的文件通過(guò)阿拉伯文的編碼標(biāo)準(zhǔn)去解析,肯定是顯示一堆亂碼。同時(shí),隨著計(jì)算機(jī)科學(xué)和互聯(lián)網(wǎng)的不斷發(fā)展,軟件國(guó)際化逐漸成為了必然的趨勢(shì)。在此背景下,一種包含了世界各地絕大部分文字字符的通用字符集就應(yīng)運(yùn)而生了-Unicode字符集。
Unicode字符集是通用多八位編碼字符集(Universal Multiple-Octet Coded Character Set)的簡(jiǎn)稱(chēng)。它為每種語(yǔ)言中的每個(gè)字符設(shè)定了統(tǒng)一并且唯一的二進(jìn)制編碼,以滿(mǎn)足跨語(yǔ)言、跨平臺(tái)進(jìn)行文本轉(zhuǎn)換、處理的要求。
下面簡(jiǎn)單梳理一下Unicode的編碼方式與實(shí)現(xiàn)方式的相關(guān)知識(shí)。
3.5.1 編碼方式
Unicode存在兩種編碼方式,分別是UCS-2和UCS-4。
- UCS-2: 采用兩個(gè)字節(jié)編碼,理論上最多可以表示216(65536)個(gè)字符。
- UCS-4: 采用四個(gè)字節(jié)編碼,理論上最多可以表示232(2147483648)個(gè)字符,完全可以涵蓋所有語(yǔ)言的字符。
將UCS-4的BMP去掉前面的兩個(gè)零字節(jié)就得到了UCS-2。在UCS-2的兩個(gè)字節(jié)前加上兩個(gè)零字節(jié),就得到了UCS-4的BMP。而目前的UCS-4規(guī)范中還沒(méi)有任何字符被分配在BMP之外。
3.5.2 實(shí)現(xiàn)方式
Unicode的實(shí)現(xiàn)方式不同于編碼方式。一個(gè)字符的Unicode編碼是確定的。但是在實(shí)際傳輸過(guò)程中,由于不同系統(tǒng)平臺(tái)的設(shè)計(jì)不一定一致,以及出于節(jié)省空間的目的,對(duì)Unicode編碼的實(shí)現(xiàn)方式有所不同。Unicode的實(shí)現(xiàn)方式稱(chēng)為Unicode轉(zhuǎn)換格式(Unicode Transformation Format,簡(jiǎn)稱(chēng)為UTF)。
常見(jiàn)的實(shí)現(xiàn)方式有UTF-8,UTF-16,UTF-32等。
- UTF-8: 以8bits(1字節(jié))為單位對(duì)UCS進(jìn)行編碼,可以用1到4個(gè)字節(jié)來(lái)表示一個(gè)字符,是一種字節(jié)變長(zhǎng)度編碼方式。
- UTF-16: 以16bits(2字節(jié))為單位對(duì)UCS進(jìn)行編碼,可以用2字節(jié)或4字節(jié)來(lái)表示一個(gè)字符,是一種字節(jié)變長(zhǎng)度編碼方式。
- UTF-32: 以32bits(4字節(jié))為單位對(duì)UCS進(jìn)行編碼,用4字節(jié)來(lái)表示一個(gè)字符,是一種字節(jié)固定長(zhǎng)度編碼方式。
UCS-2和UCS-4是編碼方案,而UTF-x是編碼實(shí)現(xiàn)方式,涉及到實(shí)際傳輸,所以需要考慮字節(jié)序問(wèn)題。
字節(jié)序(Byte Order Mark,BOM): 用于表示字節(jié)傳輸過(guò)程中的存儲(chǔ)方式,常見(jiàn)的實(shí)現(xiàn)方式及對(duì)應(yīng)BOM如下所示:
| UTF | BOM |
|---|---|
| UTF-8 | EF BB BF |
| UTF-16LE | FF FE |
| UTF-16BE | FE FF |
| UTF-32LE | FF FE 00 00 |
| UTF-32BE | 00 00 FE FF |
LE表示小端字節(jié)序,BE表示大端字節(jié)序。
3.5.3 UTF-8
由于UTF-16和UTF-32都存在空間浪費(fèi)的情況,而UTF-8采用字節(jié)為單位的變長(zhǎng)編碼方式,大大提高了空間利用率,因此,UTF-8也是我們平時(shí)用的最多的編碼方式。
UTF-8的編碼規(guī)則有兩條:
- 對(duì)于單字節(jié)的符號(hào),字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的unicode碼。因此對(duì)于英語(yǔ)字母,UTF-8編碼和ASCII碼是相同的。
- 對(duì)于n字節(jié)的符號(hào)(n>1),第一個(gè)字節(jié)的前n位都設(shè)為1,第n+1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒(méi)有提及的二進(jìn)制位,全部為這個(gè)符號(hào)的unicode碼。
下表總結(jié)了編碼規(guī)則,字母x表示可用編碼的位。
| Unicode符號(hào)范圍(十六進(jìn)制) | UTF-8編碼方式(二進(jìn)制) |
|---|---|
| 0000 0000-0000 007F | 0xxxxxxx |
| 0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
| 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
| 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
4 字符編碼的應(yīng)用
字符集與字符編碼相關(guān)的知識(shí)非常多,上面只是簡(jiǎn)單介紹了一些常見(jiàn)的字符集以及字符編碼。想要了解更多的知識(shí)可以點(diǎn)擊相關(guān)概念的鏈接進(jìn)行深入研究。
接下來(lái)介紹一下平時(shí)開(kāi)發(fā)中會(huì)涉及到編碼相關(guān)的一些知識(shí)點(diǎn)。
4.1 代碼頁(yè)
代碼頁(yè)是字符集編碼的別名,最早是IBM公司首先使用。可以將代碼頁(yè)理解為字符和字節(jié)數(shù)據(jù)的映射表。
Windows中將支持的代碼頁(yè)用一個(gè)編號(hào)來(lái)表示。例如代碼頁(yè)936就是簡(jiǎn)體中文GBK。
可以在DOS的CMD命令行下通過(guò)chcp命令進(jìn)行查看和修改系統(tǒng)的代碼頁(yè)。
# 查看代碼頁(yè)
C:\>chcp
活動(dòng)代碼頁(yè): 936
4.2 區(qū)域(Locale)設(shè)置
Microsoft為了適應(yīng)世界各地的文化背景和使用習(xí)慣,在Winodows系統(tǒng)中設(shè)計(jì)了區(qū)域設(shè)置的功能??梢酝ㄟ^(guò)控制面板->區(qū)域與語(yǔ)言選項(xiàng)進(jìn)行系統(tǒng)Locale和用戶(hù)Locale設(shè)置,其中系統(tǒng)Locale決定代碼頁(yè);用戶(hù)Locale決定數(shù)字、貨幣、時(shí)間和日期格式。設(shè)置界面如下圖所示:


C++中有兩種方式可以設(shè)置區(qū)域信息,如下:
- 通過(guò)setlocale函數(shù)在運(yùn)行時(shí)設(shè)置區(qū)域信息。
- 通過(guò)#pragrma setlocale編譯指定來(lái)設(shè)置區(qū)域信息,該指令在編譯時(shí)起作用。
4.3 VS中字符集設(shè)置
為了方便代碼的移植和統(tǒng)一,目前的開(kāi)發(fā)環(huán)境一般都會(huì)采用Uincode字符集,在VS中可以通過(guò)Project->Properties->Configuration Properities->General->Character Set進(jìn)行設(shè)置,如下圖所示:

4.4 C++中字符和字符串的相關(guān)知識(shí)
C++的新標(biāo)準(zhǔn)中引入了UTF-16和UTF-32編碼方式的字符,分別用小寫(xiě)字母u和大寫(xiě)字母U開(kāi)頭來(lái)表示,同時(shí)也引入了更多的字符串類(lèi)型與操作,直接看下MSDN提供的代碼示例:
#include <string>
using namespace std::string_literals; // enables s-suffix for std::string literals
int main()
{
// Character literals
auto c0 = 'A'; // char
auto c1 = u8'A'; // char
auto c2 = L'A'; // wchar_t
auto c3 = u'A'; // char16_t
auto c4 = U'A'; // char32_t
// String literals
auto s0 = "hello"; // const char*
auto s1 = u8"hello"; // const char*, encoded as UTF-8
auto s2 = L"hello"; // const wchar_t*
auto s3 = u"hello"; // const char16_t*, encoded as UTF-16
auto s4 = U"hello"; // const char32_t*, encoded as UTF-32
// Raw string literals containing unescaped \ and "
auto R0 = R"("Hello \ world")"; // const char*
auto R1 = u8R"("Hello \ world")"; // const char*, encoded as UTF-8
auto R2 = LR"("Hello \ world")"; // const wchar_t*
auto R3 = uR"("Hello \ world")"; // const char16_t*, encoded as UTF-16
auto R4 = UR"("Hello \ world")"; // const char32_t*, encoded as UTF-32
// Combining string literals with standard s-suffix
auto S0 = "hello"s; // std::string
auto S1 = u8"hello"s; // std::string
auto S2 = L"hello"s; // std::wstring
auto S3 = u"hello"s; // std::u16string
auto S4 = U"hello"s; // std::u32string
// Combining raw string literals with standard s-suffix
auto S5 = R"("Hello \ world")"s; // std::string from a raw const char*
auto S6 = u8R"("Hello \ world")"s; // std::string from a raw const char*, encoded as UTF-8
auto S7 = LR"("Hello \ world")"s; // std::wstring from a raw const wchar_t*
auto S8 = uR"("Hello \ world")"s; // std::u16string from a raw const char16_t*, encoded as UTF-16
auto S9 = UR"("Hello \ world")"s; // std::u32string from a raw const char32_t*, encoded as UTF-32
}
4.5 ANSI字符串與Unicode字符串相互轉(zhuǎn)換
Windows提供了一些列的API函數(shù)來(lái)操作字符串,包括獲取字符集信息,判斷是否是DBCS的起始字節(jié)以及ANSI字符串與Unicode字符串之間相互轉(zhuǎn)換等。用的比較多的應(yīng)該就是字符串轉(zhuǎn)換的API了,如下所示:
- MultiByteToWideChar: ANSI字符串轉(zhuǎn)換成Unicode字符串。
- WideCharToMultiByte: Unicode字符串轉(zhuǎn)換成ANSI字符串。
ANSI字符串又稱(chēng)為多字節(jié)字符串,Unicode字符串又稱(chēng)為寬字節(jié)字符串。每個(gè)人的叫法習(xí)慣不同,知道對(duì)應(yīng)的關(guān)系即可。
為了操作簡(jiǎn)單,ATL提供了幾個(gè)宏用于字符串轉(zhuǎn)換,底層實(shí)現(xiàn)都是通過(guò)上述介紹的MultiByteToWideChar和WideCharToMultiByte兩個(gè)API。
平時(shí)用的最多的就是CA2T和CT2A,這兩個(gè)宏中各個(gè)字母代表的含義如下所示:
| 字母 | 含義 |
|---|---|
| C | 目標(biāo)類(lèi)型必須是Const類(lèi)型 |
| A | ANSI字符串 |
| W | Unicode字符串 |
| T | 通用字符串,當(dāng)定義了_UNICODE宏時(shí)T表示W(wǎng),否則T表示A |
使用這兩個(gè)宏的時(shí)候需要注意幾點(diǎn):
- 作用域問(wèn)題:CA2T和CT2A的轉(zhuǎn)換后的數(shù)據(jù)作用域只在當(dāng)前行,即在下一行代碼中再去訪(fǎng)問(wèn)轉(zhuǎn)換后的數(shù)據(jù)會(huì)出現(xiàn)不可預(yù)知的問(wèn)題。如下代碼所示:
// 正確,F(xiàn)un函數(shù)使用轉(zhuǎn)換后的TCHAR
Fun(CA2T(szSrc, CP_UTF8));
// 正確,轉(zhuǎn)換后的數(shù)據(jù)賦值給strDes
CString strDes = CA2T(szSrc, CP_UTF8);
// 錯(cuò)誤,轉(zhuǎn)換后的數(shù)據(jù)在下一行被釋放,即szDes指向的數(shù)據(jù)變成未知
TCHAR *szDes = CA2T(szSrc, CP_UTF8);
- 參數(shù)問(wèn)題: 轉(zhuǎn)換過(guò)程中可以指定code page信息,下面是摘之winnls.h對(duì)應(yīng)參數(shù)的描述:
// Code Page Default Values.
#define CP_ACP 0 // default to ANSI code page
#define CP_OEMCP 1 // default to OEM code page
#define CP_MACCP 2 // default to MAC code page
#define CP_THREAD_ACP 3 // current thread's ANSI code page
#define CP_SYMBOL 42 // SYMBOL translations
#define CP_UTF7 65000 // UTF-7 translation
#define CP_UTF8 65001 // UTF-8 translation
下面以CA2T為例,我們來(lái)看下底層實(shí)現(xiàn)是如何運(yùn)用這些參數(shù)的。
// 1. CA2T其實(shí)轉(zhuǎn)化為CA2W
#define CA2T CA2W
// 2. CA2W又是通過(guò)模板CA2WEX來(lái)實(shí)現(xiàn)
typedef CA2WEX<> CA2W;
// 3. 下面是CA2WEX<>的模板實(shí)現(xiàn):
template< int t_nBufferLength = 128 >
class CW2AEX
{
public:
CW2AEX(_In_z_ LPCWSTR psz) throw(...)
:m_psz( m_szBuffer )
{
Init( psz, _AtlGetConversionACP() );
}
CW2AEX(_In_z_ LPCWSTR psz, _In_ UINT nCodePage) throw(...)
:m_psz( m_szBuffer )
{
Init( psz, nCodePage );
}
~CW2AEX() throw()
{
AtlConvFreeMemory(m_psz,m_szBuffer,t_nBufferLength);
}
_Ret_z_ operator LPSTR() const throw()
{
return( m_psz );
}
private:
void Init(_In_z_ LPCWSTR psz, _In_ UINT nConvertCodePage) throw(...)
{
if (psz == NULL)
{
m_psz = NULL;
return;
}
int nLengthW = lstrlenW( psz )+1;
int nLengthA = nLengthW*4;
AtlConvAllocMemory(&m_psz,nLengthA,m_szBuffer,t_nBufferLength);
BOOL bFailed=(0 == ::WideCharToMultiByte( nConvertCodePage, 0, psz, nLengthW, m_psz, nLengthA, NULL, NULL ));
if (bFailed)
{
if (GetLastError()==ERROR_INSUFFICIENT_BUFFER)
{
nLengthA = ::WideCharToMultiByte( nConvertCodePage, 0, psz, nLengthW, NULL, 0, NULL, NULL );
AtlConvAllocMemory(&m_psz,nLengthA,m_szBuffer,t_nBufferLength);
bFailed=(0 == ::WideCharToMultiByte( nConvertCodePage, 0, psz, nLengthW, m_psz, nLengthA, NULL, NULL ));
}
}
if (bFailed)
{
AtlThrowLastWin32();
}
}
public:
LPSTR m_psz;
char m_szBuffer[t_nBufferLength];
private:
CW2AEX(_In_ const CW2AEX&) throw();
CW2AEX& operator=(_In_ const CW2AEX&) throw();
};
inline UINT WINAPI _AtlGetConversionACP() throw()
{
#ifdef _CONVERSION_DONT_USE_THREAD_LOCALE
return CP_ACP;
#else
return CP_THREAD_ACP;
#endif
}
- 從上述的代碼邏輯中可以看出,當(dāng)沒(méi)有指定轉(zhuǎn)換代碼頁(yè)的時(shí)候默認(rèn)通過(guò)_AtlGetConversionACP函數(shù)來(lái)獲取轉(zhuǎn)換參數(shù)。
- 注意區(qū)別CP_ACP和CP_THREAD_ACP,大多數(shù)情況下這兩者對(duì)應(yīng)的代碼頁(yè)是一樣的,都是系統(tǒng)當(dāng)前的代碼頁(yè),但是如果程序在運(yùn)行時(shí)指定了其它的代碼頁(yè)則會(huì)出現(xiàn)不一致的情況。
- CP_ACP代表的是系統(tǒng)當(dāng)前的代碼頁(yè),但是不同系統(tǒng)中當(dāng)前的代碼頁(yè)可能是不一樣的,例如A電腦設(shè)置的是936(簡(jiǎn)體中文),B電腦設(shè)置的是950(繁體中文),此時(shí)將一個(gè)簡(jiǎn)體中文字符串進(jìn)行轉(zhuǎn)換時(shí)在A(yíng)電腦上可以成功,但是在B電腦上就出現(xiàn)了亂碼情況。因此,建議采用CP_UTF8參數(shù)對(duì)字符串進(jìn)行轉(zhuǎn)換,降低出現(xiàn)亂碼的概率。
更多詳細(xì)內(nèi)容請(qǐng)參閱ATL and MFC String Conversion Macros。
5 總結(jié)
字符集和字符編碼相關(guān)的知識(shí)非常多,本文主要梳理總結(jié)了一些常用的、比較核心的概念,希望對(duì)大家有所幫助。
整理這篇文章的過(guò)程中查看了非常多的資料,主要是維基百科和MSDN文檔,大部分可以通過(guò)文中超鏈接跳轉(zhuǎn)過(guò)去,還有一些博客對(duì)字符編碼的介紹,下面列舉一些個(gè)人覺(jué)得總結(jié)的不錯(cuò)的文章作為補(bǔ)充閱讀。