C/C++ 編程中多國(guó)語(yǔ)言處理
出處:jinzhao 發(fā)布于:2010-04-13 14:42:47
摘要:多國(guó)語(yǔ)言的存在,使程序員在編碼處理上花費(fèi)了大量時(shí)間和精力;然而各種各樣的亂碼問(wèn)題,如 XML 格式錯(cuò)誤、文本顯示異常、解析器異常等依然層出不窮。特別的,相對(duì)于 JAVA 語(yǔ)言,C/C++ 在處理編碼問(wèn)題上有更大的困難。本文避免糾纏不同編碼格式的具體異同,以 Unicode 為,以簡(jiǎn)體中文為例,從工程應(yīng)用角度分析編碼問(wèn)題存在的原因,不僅提出 C/C++ 標(biāo)準(zhǔn)庫(kù)編程的解決方案,更結(jié)合項(xiàng)目經(jīng)驗(yàn),總結(jié)出處理多國(guó)語(yǔ)言編碼問(wèn)題的一般思路。
問(wèn)題的提出
多國(guó)語(yǔ)言的存在、不同語(yǔ)言操作系統(tǒng)的存在,使得針對(duì)多語(yǔ)言的設(shè)計(jì)頗費(fèi)周章,在編碼上所付出的工作量也是可觀的。所謂編碼的問(wèn)題,歸結(jié)起來(lái),就是二進(jìn)制的編碼以何種編碼格式進(jìn)行解析的問(wèn)題。特別是在硬盤文件和內(nèi)存數(shù)據(jù)的相互轉(zhuǎn)化、即讀寫過(guò)程中,如果采用了錯(cuò)誤的編碼格式,就會(huì)造成亂碼。JAVA 語(yǔ)言在字符串、編碼等處理方面給了程序員更為直接、方便的接口,習(xí)慣使用 JAVA 做編碼的程序員,在使用 C/C++ 進(jìn)行文本編碼相關(guān)的操作時(shí),常會(huì)感到困惑。本文的目的在于以常用的 Unicode(UCS-2)、GB2312、UTF8 三種編碼為例,分析不同編碼在實(shí)用中的關(guān)系,特別是 C/C++ 中,怎樣處理各種編碼的問(wèn)題。
編碼處理常見的問(wèn)題
1. 將內(nèi)存中編碼 A 的字符串以編碼 B 格式處理成字節(jié)流寫入文件
2. 將原本以 A 編碼組成的文件以字節(jié)流形式讀入內(nèi)存、并以編碼 B 解析為字符串。
種情況,可能造成數(shù)據(jù)的變化、失真。
如果使用 JAVA 語(yǔ)言,發(fā)生這種錯(cuò)誤的情況稍少一些,因?yàn)樵?JAVA 中沒有 wstring 這種概念,在內(nèi)存中的 String,使用的編碼都是 Unicode,其中的轉(zhuǎn)換對(duì)于程序員來(lái)講是透明的。只要使用輸入 / 輸出方法時(shí)注意字節(jié)流的字符集選擇即可。
例如,編碼為中文 GB2312 的“標(biāo)準(zhǔn)”字符串被讀入內(nèi)存后轉(zhuǎn)存為 UTF8 的過(guò)程:

圖 1. 文件轉(zhuǎn)換編碼的 JAVA 處理方式
但 C/C++ 編程,由于通常使用 char、string 類型的時(shí)候比較多,特別是進(jìn)行文件讀寫,基本都是操作 char* 類型的數(shù)據(jù)。并且也沒有像 JAVA 中 getByte(String charsetname) 這種函數(shù),不能直接根據(jù)字符集重新編碼得到字符串的 byte 數(shù)組。這時(shí)候,我們使用的 string 其實(shí)就一般不是 Unicode,而是符合某種編碼表的。這使得我們往往困惑于 string 的編碼問(wèn)題。假設(shè)有 utf8 的字符串“一”(E4 B8 80),而我們錯(cuò)誤的認(rèn)為它是符合 gb2312(編碼 A)的,并將其轉(zhuǎn)換為 utf8(編碼 B),這種轉(zhuǎn)換結(jié)果是破壞性的,錯(cuò)誤的輸出將永遠(yuǎn)無(wú)法正確識(shí)別。
依然以“標(biāo)準(zhǔn)”為例,這是一個(gè)正確的轉(zhuǎn)換:

圖 2. 文件轉(zhuǎn)換編碼的 C/C++ 處理方式
第二種情況,則是更常見到的。例如:瀏覽器瀏覽網(wǎng)頁(yè)時(shí)的發(fā)生的亂碼問(wèn)題;在寫 XML 文件時(shí),指定了 < ?xml version="1.0" encoding="utf-8" ?> 然而文件中卻包含 GB2312 的字符串——這樣經(jīng)常會(huì)導(dǎo)致 XML 文件 bad formatted,而使得解析器出錯(cuò)。
這種情況下,其實(shí)數(shù)據(jù)都是正確的,只要瀏覽器選擇正確的編碼,將 XML 文件中的 GB2312 轉(zhuǎn)換為 UTF8 或者修改 encoding,就可以解決問(wèn)題。
需要注意的是,ASCII 碼的字符,即單字節(jié)字符,一般不受編碼變動(dòng)影響,在所有編碼表中的值是一樣的;需要小心處理的是多字節(jié)字符,例如中文語(yǔ)言。
編碼轉(zhuǎn)換方法
一般的編碼轉(zhuǎn)換,直接做映射的不太可能,需要比較多的工作量,大多情況下還是選擇 Unicode 作為轉(zhuǎn)換的中介。
使用庫(kù)函數(shù)
如前文所說(shuō),JAVA 的 String 對(duì)象是以 Unicode 編碼存在的,所以 JAVA 程序員主要關(guān)心的是讀入時(shí)判斷字節(jié)流的編碼,從而確??梢哉_的轉(zhuǎn)化為 Unicode 編碼;相比之下,C/C++ 將外部文件讀出的數(shù)據(jù)存為字符數(shù)組、或者是 string 類型;而 wstring 才是符合 Unicode 編碼的雙字節(jié)數(shù)組。一般常用的方法是 C 標(biāo)準(zhǔn)庫(kù)的 wcstombs、mbstowcs 函數(shù),和 windows API 的 MultiByteToWideChar 與 WideCharToMultiByte 函數(shù)來(lái)完成向 Unicode 的轉(zhuǎn)入和轉(zhuǎn)出。
這里以 MBs2WCs 函數(shù)的實(shí)現(xiàn)說(shuō)明 GB2312 向 Unicode 的轉(zhuǎn)換的主要過(guò)程:
清單 1. 多字節(jié)字符串向?qū)捵止?jié)字符串轉(zhuǎn)換
wchar_t * MBs2WCs(const char* pszSrc){
wchar_t* pwcs = NULL;
intsize = 0;
#ifdefined(_linux_)
setlocale(LC_ALL, "zh_CN.GB2312");
size = mbstowcs(NULL,pszSrc,0);
pwcs = new wchar_t[size+1];
size = mbstowcs(pwcs, pszSrc, size+1);
pwcs[size] = 0;
#else
size = MultiByteToWideChar(20936, 0, pszSrc, -1, 0, 0);
if(size <= 0)
returnNULL;
pwcs = new wchar_t[size];
MultiByteToWideChar(20936, 0, pszSrc, -1, pwcs, size);
#endif
returnpwcs;
}
相應(yīng)的,WCs2MBs 可以將寬字符串轉(zhuǎn)化為字節(jié)流。
清單 2. 寬字節(jié)字符串向多字節(jié)字符串轉(zhuǎn)換
char* WCs2MBs(const wchar_t * wcharStr){
char* str = NULL;
intsize = 0;
#ifdefined(_linux_)
setlocale(LC_ALL, "zh_CN.UTF8");
size = wcstombs( NULL, wcharStr, 0);
str = new char[size + 1];
wcstombs( str, wcharStr, size);
str[size] = '\0';
#else
size = WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, NULL, NULL, NULL, NULL );
str = new char[size];
WideCharToMultiByte( CP_UTF8, 0, wcharStr, -1, str, size, NULL, NULL );
#endif
returnstr;
}
Linux 的 setlocale 的具體使用可以參閱有 C/C++ 文檔,它關(guān)系到文字、貨幣單位、時(shí)間等很多格式問(wèn)題。Windows 相關(guān)的代碼中 20936 和宏定義 CP_UTF8 是 GB2312 編碼對(duì)應(yīng)的的 Code Page[ 類似的 Code Page 參數(shù)可以從 MSDN的 Encoding Class 有關(guān)信息中獲得 ]。
這里需要特別指出的是 setlocale 的第二個(gè)參數(shù),Linux 和 Windows 是不同的:
1. 筆者在 Eclipse CDT + MinGW 下使用 [country].[charset](如 zh_CN.gb2312 或 zh_CN.UTF8)的格式并不能通過(guò)編碼轉(zhuǎn)換測(cè)試,但可以使用 Code Page,即可以寫成 setlocale(LC_ALL, ".20936") 這樣的代碼。這說(shuō)明,這個(gè)參數(shù)與編譯器無(wú)關(guān),而與系統(tǒng)定義有關(guān),而不同操作系統(tǒng)對(duì)于已安裝字符集的定義是不同的。
2. Linux 系統(tǒng)下可以參見 /usr/lib/locale/ 路徑,系統(tǒng)所支持的 locale 都在這里。轉(zhuǎn)換成 UTF8 時(shí),并不需要 [country] 部分一定是 zh_CN,en_US.UTF8 也可以正常轉(zhuǎn)換。
另外,標(biāo)準(zhǔn) C 和 Win32 API 函數(shù)返回值是不同的,標(biāo)準(zhǔn) C 返回的 wchar_t 數(shù)組或者是 char 數(shù)組都沒有字符串結(jié)束符,需要手動(dòng)賦值,所以 Linux 部分的代碼要有區(qū)別對(duì)待。
,還要注意應(yīng)當(dāng)在調(diào)用這兩個(gè)函數(shù)后釋放分配的空間。如果將 MBs2WCs 和 WCs2MBs 的返回值分別轉(zhuǎn)化為 wstring 和 string,就可以在它們函數(shù)體內(nèi)做 delete,這里為了代碼簡(jiǎn)明,故而省略,但請(qǐng)讀者別忘記。
第三方庫(kù)
目前的第三方工具已經(jīng)比較完善,這里介紹兩個(gè),本文側(cè)重點(diǎn)不在此,不對(duì)其做太多探討。
Linux 上存在第三方的 iconv 項(xiàng)目,使用也較為簡(jiǎn)單,其實(shí)質(zhì)也是以 Unicode 作為轉(zhuǎn)換的中介。
ICU 是一個(gè)很完善的國(guó)際化工具。其中的 Code Page Conversion 功能也可以支持文本數(shù)據(jù)從任何字符集向 Unicode 的雙向轉(zhuǎn)換。
實(shí)驗(yàn)測(cè)試
在代碼中調(diào)用“編碼轉(zhuǎn)換方法”一節(jié)里提到的函數(shù),將 gb2312 編碼的字符串轉(zhuǎn)換為 UTF8 編碼,分析其編碼轉(zhuǎn)換的行為:
在英文 Linux 環(huán)境下,執(zhí)行下列命令:
export LC_ALL=zh_CN.gb2312
然后編譯并執(zhí)行以下程序(其中漢字都是在 gb2312 環(huán)境中寫入源文件)
L1: wstring ws = L"一";
L2: string s_gb2312 = "一";
L3: wchar_t * wcs = MBs2WChar(s_gb2312.c_str());
L4: char* cs = WChar2MBs(wcs);
查看輸出:
L1 - 1 wide char: 0x04BB
L2 - 2 bytes:0xd2,0xbb,即 gb2312 編碼 0xD2BB
L3 - 返回的 wchar_t 數(shù)組內(nèi)容為 0x4E00,也就是 Unicode 編碼
L4 - 將 Unicode 再度轉(zhuǎn)換為 UTF8 編碼,輸出的字符長(zhǎng)度為 3,即 0xE4,oxB8,0x80
在 L1 行,執(zhí)行結(jié)果顯示編碼為一個(gè) 0x04bb,其實(shí)這是一個(gè)轉(zhuǎn)換錯(cuò)誤,如果使用其他漢字,如“哈”,編譯都將無(wú)法通過(guò)。也就是說(shuō) Linux 環(huán)境下,直接聲明中文寬字符串是不正確的,編譯器不能夠正確轉(zhuǎn)換。
而在中文 windows 下使用相同測(cè)試代碼,則會(huì)在 L1 處出現(xiàn)區(qū)別,ws 中的 wchar_t 元素十六進(jìn)制值是 0x4e00,這是漢字“一”的 Unicode 編碼。
處理編碼問(wèn)題的經(jīng)驗(yàn)總結(jié)
首先,這里先簡(jiǎn)單說(shuō)明一下 Unicode 和 UTF8 的關(guān)系:Unicode 的實(shí)現(xiàn)方式和它的編碼方式并不相同,UTF8 就是其實(shí)現(xiàn)之一。比方使用 UltraEdit 打開 UTF8 編碼的中文文件,使用 16 進(jìn)制查看,可以發(fā)現(xiàn)看到的中文對(duì)應(yīng)部分應(yīng)當(dāng)是 Unicode 編碼,每個(gè)中文字長(zhǎng)度 2 字節(jié)—— UltraEdit 在這里已經(jīng)做了轉(zhuǎn)化;如果直接查看其二進(jìn)制文件,可以發(fā)現(xiàn)是 3 字節(jié)。但兩者的差別僅在于 Unicode 向 UTF8 做了數(shù)學(xué)上的轉(zhuǎn)化。
其次,關(guān)于第三方庫(kù)的選擇,應(yīng)當(dāng)綜合考慮項(xiàng)目的需求。一般的文本字符轉(zhuǎn)換,系統(tǒng)的庫(kù)函數(shù)已經(jīng)可以滿足需求,實(shí)現(xiàn)也很簡(jiǎn)單;如果需要針對(duì)不同地區(qū)的語(yǔ)言、文字、習(xí)慣進(jìn)行編程,需要更為豐富的功能,當(dāng)然選擇成熟的第三方工具可以事半功倍。
,從邏輯上保持字符串的編碼正確,需要注意幾條一般規(guī)律:
編碼選擇:多國(guó)語(yǔ)言環(huán)境的編程,以使用 UTF 編碼為原則,減少字符集轉(zhuǎn)換。
string 并不包含編碼信息,但是編碼確定了 string 的二進(jìn)制內(nèi)容。
讀寫一致:讀入時(shí)使用的字符集要與寫出時(shí)使用的一致。如果不需要改變字符串內(nèi)容,僅僅是將字符串讀入、再寫出,建議不要調(diào)整任何字符集——即使程序使用的系統(tǒng)默認(rèn)字符集 A 與文件的實(shí)際編碼 B 不符合,寫出的字符串依然會(huì)是正確的 B 編碼。
讀入已知:對(duì)于必須處理、解析或顯示的字符串,從文件讀入時(shí)必須知道它的編碼,避免處理字符串的代碼簡(jiǎn)單使用系統(tǒng)默認(rèn)字符集;即便對(duì)于程序從系統(tǒng)中收集到的內(nèi)存字符串,也應(yīng)知道其符合的編碼格式——一般為系統(tǒng)默認(rèn)字符集。
避免直接使用 Unicode:這里是說(shuō)將非 ASCII 編碼的 16 進(jìn)制或者 10 進(jìn)制數(shù)值用 &# 與 ; 包含起來(lái)的使用方式,例如將中文“一”寫成“e00;”。這種方法的實(shí)質(zhì)是 Unicode 編碼直接寫入文件。這不僅會(huì)降低代碼的通用性、輸出文件的可讀性,處理起來(lái)也很困難。比如法文字符在其他字符集中是大于 80H 的單字節(jié)字符,程序同時(shí)要支持中文的時(shí)候,很有可能會(huì)將多字節(jié)的中文字符錯(cuò)誤割裂。
避免陷入直接的字符集編程:國(guó)際化、本地化的工具已經(jīng)比較成熟,非純粹做編碼轉(zhuǎn)換的程序員沒有必要自己去處理不同編碼表的映射轉(zhuǎn)換問(wèn)題。
Unicode/UTF8 并不能解決一切亂碼問(wèn)題:Unicode 可以說(shuō)是將世界語(yǔ)言統(tǒng)一起來(lái)的一套編碼。但是這并不意味著在一個(gè)系統(tǒng)中可以正常顯示的按照 UTF8 編碼的文件,在另一個(gè)系統(tǒng)中也可以正常顯示。例如,在中文的 UTF8 編碼或者 Unicode 編碼在沒有東亞語(yǔ)言包支持的法文系統(tǒng)中,依然是不可識(shí)別的亂碼——盡管 UTF8、Unicode 它們都支持。
版權(quán)與免責(zé)聲明
凡本網(wǎng)注明“出處:維庫(kù)電子市場(chǎng)網(wǎng)”的所有作品,版權(quán)均屬于維庫(kù)電子市場(chǎng)網(wǎng),轉(zhuǎn)載請(qǐng)必須注明維庫(kù)電子市場(chǎng)網(wǎng),http://m.58mhw.cn,違反者本網(wǎng)將追究相關(guān)法律責(zé)任。
本網(wǎng)轉(zhuǎn)載并注明自其它出處的作品,目的在于傳遞更多信息,并不代表本網(wǎng)贊同其觀點(diǎn)或證實(shí)其內(nèi)容的真實(shí)性,不承擔(dān)此類作品侵權(quán)行為的直接責(zé)任及連帶責(zé)任。其他媒體、網(wǎng)站或個(gè)人從本網(wǎng)轉(zhuǎn)載時(shí),必須保留本網(wǎng)注明的作品出處,并自負(fù)版權(quán)等法律責(zé)任。
如涉及作品內(nèi)容、版權(quán)等問(wèn)題,請(qǐng)?jiān)谧髌钒l(fā)表之日起一周內(nèi)與本網(wǎng)聯(lián)系,否則視為放棄相關(guān)權(quán)利。
- EDA技術(shù)工具鏈與全流程設(shè)計(jì)運(yùn)維指南2026/1/5 10:28:51
- PLC程序現(xiàn)場(chǎng)疑難問(wèn)題排查與深度優(yōu)化指南2025/12/24 14:36:36
- PLC程序現(xiàn)場(chǎng)調(diào)試與優(yōu)化實(shí)操指南2025/12/24 14:29:57
- 工業(yè)PLC模擬量信號(hào)采集:調(diào)理技術(shù)與抗干擾工程方案2025/12/15 14:39:08
- PLC設(shè)備如何選型2025/9/5 17:15:14









