字符集和字符編碼知識(shí)梳理

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é),字符串,字符集和字符編碼等基本概念。

  1. 字符(Character): 各種文字和符號(hào)的總稱(chēng),包括各國(guó)家文字、標(biāo)點(diǎn)符號(hào)、圖形符號(hào)、數(shù)字等。
  2. 字節(jié)(Byte): 計(jì)算機(jī)信息技術(shù)用于計(jì)量存儲(chǔ)容量的一種計(jì)量單位,也表示一些計(jì)算機(jī)編程語(yǔ)言中的數(shù)據(jù)類(lèi)型和語(yǔ)言字符。
  3. 字符串(string): 一個(gè)連續(xù)的字符序列,在存儲(chǔ)上類(lèi)似于字符數(shù)組。
  4. 字符集(Character Set): 多個(gè)字符的集合,字符集種類(lèi)較多,每個(gè)字符集包含的字符個(gè)數(shù)不同。
  5. 字符編碼(Character encoding): 也稱(chēng)字集碼,是把字符集中的字符編碼為指定集合中某一對(duì)象(例如:比特模式、自然數(shù)序列、8位組或者電脈沖),以便文本在計(jì)算機(jī)中存儲(chǔ)和通過(guò)通信網(wǎng)絡(luò)的傳遞。
  6. 單字節(jié)字符集(Single Byte Character Set, SBCS): 所有字符都只用一個(gè)字節(jié)表示。用一個(gè)字節(jié)表示的0來(lái)標(biāo)志SBCS字符串的結(jié)束。
  7. 多字節(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é)束。
  8. 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)字符集及字符編碼

上圖中只列舉了幾種常見(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)行編碼:

  1. 首先構(gòu)造一個(gè)94行94列的方陣,對(duì)每一行稱(chēng)為一個(gè)“區(qū)”,每一列稱(chēng)為一個(gè)“位”,
  2. 然后將所有字符依照下表的規(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。編碼范圍如下所示:
GBK編碼范圍
  • 雙字節(jié)符號(hào)可以表達(dá)的64K空間如下圖所示。綠色和黃色區(qū)域是GBK的編碼,紅色是用戶(hù)定義區(qū)域。沒(méi)有顏色區(qū)域是不正確的代碼組合。
GBK編碼空間

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ū)段。編碼范圍如下圖所示:


GB18030編碼范圍

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-2UCS-4。

  1. UCS-2: 采用兩個(gè)字節(jié)編碼,理論上最多可以表示216(65536)個(gè)字符。
  2. UCS-4: 采用四個(gè)字節(jié)編碼,理論上最多可以表示232(2147483648)個(gè)字符,完全可以涵蓋所有語(yǔ)言的字符。
    • UCS-4根據(jù)最高位為0的最高字節(jié)分成2^7=128個(gè)group。
    • 每個(gè)group再根據(jù)次高字節(jié)分為256個(gè)plane。
    • 每個(gè)plane根據(jù)第3個(gè)字節(jié)分為256行 (rows),每行包含256個(gè)cells。
    • group 0的plane 0被稱(chēng)作Basic Multilingual Plane, 即BMP

將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等。

  1. UTF-8: 以8bits(1字節(jié))為單位對(duì)UCS進(jìn)行編碼,可以用1到4個(gè)字節(jié)來(lái)表示一個(gè)字符,是一種字節(jié)變長(zhǎng)度編碼方式。
  2. UTF-16: 以16bits(2字節(jié))為單位對(duì)UCS進(jìn)行編碼,可以用2字節(jié)或4字節(jié)來(lái)表示一個(gè)字符,是一種字節(jié)變長(zhǎng)度編碼方式。
  3. 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ī)則有兩條:

  1. 對(duì)于單字節(jié)的符號(hào),字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的unicode碼。因此對(duì)于英語(yǔ)字母,UTF-8編碼和ASCII碼是相同的。
  2. 對(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è)置界面如下圖所示:

用戶(hù)Locale

系統(tǒng)Locale

C++中有兩種方式可以設(shè)置區(qū)域信息,如下:

  1. 通過(guò)setlocale函數(shù)在運(yùn)行時(shí)設(shè)置區(qū)域信息。
  2. 通過(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è)置,如下圖所示:

VS中字符集設(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了,如下所示:

  1. MultiByteToWideChar: ANSI字符串轉(zhuǎn)換成Unicode字符串。
  2. 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):

  1. 作用域問(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);
  1. 參數(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ǔ)充閱讀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 字符集和編碼簡(jiǎn)介 在編程中常常可以見(jiàn)到各種字符集和編碼,包括ASCII,MBCS,Unicode等字符集。確切的說(shuō)...
    蘭山小亭閱讀 9,075評(píng)論 0 13
  • 摘要:本文從Unicode入手,介紹由于通信問(wèn)題而產(chǎn)生的字符集,以及Unicode的發(fā)展情況。介紹各種字符集的及其...
    瘋狂的冰塊閱讀 2,373評(píng)論 0 6
  • 空氣中彌漫著淡淡的青草的味道,微風(fēng)拂過(guò)帶來(lái)絲絲縷縷的花香。日光在樹(shù)葉的間隙中跳出,落在一幢精巧的竹樓上。竹樓前面是...
    愛(ài)上路燈閱讀 252評(píng)論 0 0
  • 學(xué)校每周四下午是社團(tuán)活動(dòng),蓓兒參加了“美好生活”社團(tuán)。社團(tuán)每周主題不一樣,有做手工的、有做好吃的,還有學(xué)攝影的...
    我們的日常閱讀 619評(píng)論 7 9
  • 偏遠(yuǎn)的小城市里有一道透明的玻璃墻,玻璃墻內(nèi)住著一群小丑,他們帶著紅卷毛藍(lán)卷毛頭套,臉像刷了一層白漿,咧著血盆大口,...
    黎巴嫩的香柏樹(shù)閱讀 407評(píng)論 0 0

友情鏈接更多精彩內(nèi)容