對C++ Local的經(jīng)典分析

本貼轉(zhuǎn)載自:再別流年的技術(shù)實(shí)驗(yàn)室

文章地址:  http://kittsoft.xp3.biz/?p=86

“這個(gè)問題比你想象中復(fù)雜”
(我也學(xué)下BS的風(fēng)格,雖然這句話是我自己臨時(shí)想說的。^^)
從字符到整數(shù)
char是一種整數(shù)類型,這句話的含義是,char所能表示的字符在C/C++中都是整數(shù)類型。好,接下來,很多文章就會(huì)舉出一個(gè)典型例子,比如,’a' 的數(shù)值就是0×61。這種說法對嗎?如果你細(xì)心的讀過K&R和BS對于C和C++描述的原著,你就會(huì)馬上反駁道,0×61只是’a'的ASCII 值,并沒有任何規(guī)定C/C++的char值必須對應(yīng)ASCII。C/C++甚至沒有規(guī)定char占幾位,只是規(guī)定了sizeof(char)等于1。
當(dāng)然,目前大部分情況下,char是8位的,并且,在ASCII范圍內(nèi)的值,與ASCII對應(yīng)。
本地化策略集(locale)
“將’a'翻譯成0×61的整數(shù)值”,“將ASCII范圍內(nèi)的編碼與char的整數(shù)值對應(yīng)起來”,類似這樣的規(guī)定,是特定系統(tǒng)和特定編譯器制定 的,C/C++中有個(gè)特定的名詞來描述這種規(guī)定的集合:本地化策略集(locale。也有翻譯成“現(xiàn)場”)。而翻譯——也就是代碼轉(zhuǎn)換(codecvt) 只是這個(gè)集合中的一個(gè),C++中定義為策略(facet。也有翻譯為“刻面”)
C/C++的編譯策略
“本地化策略集”是個(gè)很好的概念,可惜在字符和字符串這個(gè)層面上,C/C++并不使用(C++的locale通常只是影響流(stream)),C/C++使用更直接簡單的策略:硬編碼。
簡單的說,字符(串)在程序文件(可執(zhí)行文件,非源文件)中的表示,與在程序執(zhí)行中在內(nèi)存中的表示一致。考慮兩種情況:
A、char c = 0×61;
B、char c = ‘a’;
情況A下,編譯器可以直接認(rèn)識作為整數(shù)的c,但是在情況B下,編譯器必須將’a'翻譯成整數(shù)。編譯器的策略也很簡單,就是直接讀取字符(串)在源文件中的編碼數(shù)值。比如:
const char* s = “中文abc”;
這段字符串在GB2312(Windows 936),也就是我們的windows默認(rèn)中文系統(tǒng)源文件中的編碼為:
0xD6   0xD0   0xCE 0xC4 0×61 0×62 0×63
在UTF-8,也就是Linux默認(rèn)系統(tǒng)源文件中的編碼為:
0xE4   0xB8   0xAD   0xE6   0×96   0×87   0×61   0×62   0×63
一般情況下,編譯器會(huì)忠實(shí)于源文件的編碼為s賦值,例外的情況比如VC會(huì)自作聰明的把大部分其他類型編碼的字符串轉(zhuǎn)換成GB2312(除了像UTF-8 without signature這樣的幸存者)。
程序在執(zhí)行的時(shí)候,s也就保持是這樣的編碼,不會(huì)再做其他的轉(zhuǎn)換。
寬字符 wchar_t
正如char沒有規(guī)定大小,wchar_t同樣沒有標(biāo)準(zhǔn)限定,標(biāo)準(zhǔn)只是要求一個(gè)wchar_t可以表示任何系統(tǒng)所能認(rèn)識的字符,在win32 中,wchar_t為16位;Linux中是32位。wchar_t同樣沒有規(guī)定編碼,因?yàn)閁nicode的概念我們后面才解釋,所以這里只是提一下,在 win32中,wchar_t的編碼是UCS-2BE;而Linux中是UTF-32BE(等價(jià)于UCS-4BE),不過簡單的說,在16位以內(nèi),一個(gè)字 符的這3種編碼值是一樣的。因此:
const wchar_t* ws = L”中文abc”;
的編碼分別為:
0x4E2D   0×6587    0×0061   0×0062   0×0063                                                //win32,16位
0x00004E2D   0×00006587    0×00000061   0×00000062   0×00000063        //Linux,32位
大寫的L是告訴編譯器:這是寬字符串。所以,這時(shí)候是需要編譯器根據(jù)locale來進(jìn)行翻譯的。
比如,在Windows環(huán)境中,編譯器的翻譯策略是GB2312到UCS-2BE;Linux環(huán)境中的策略是UTF-8到UTF-32BE。
這時(shí)候就要求源文件的編碼與編譯器的本地化策略集中代碼翻譯的策略一致,例如VC只能讀取GB2312的源代碼(這里還是例外,VC太自作聰明了 ,會(huì)將很多其他代碼在編譯時(shí)自動(dòng)轉(zhuǎn)換成GB2312),而gcc只能讀取UTF-8的源代碼(這里就有個(gè)尷尬,MinGW運(yùn)行win32下,所以只有 GB2312系統(tǒng)才認(rèn);而MinGW卻用gcc編寫,所以自己只認(rèn)UTF-8,所以結(jié)果就是,MinGW的寬字符被廢掉了)。
寬字符(串)由編譯器翻譯,還是被硬編碼進(jìn)程序文件中。

Unicode和UCS
Unicode和UCS是兩個(gè)獨(dú)立的組織分別制定的一套編碼標(biāo)準(zhǔn),但是因?yàn)闅v史的原因,這兩套標(biāo)準(zhǔn)是完全一樣的。Unicode這個(gè)詞用得比較多的原因可 能是因?yàn)楸容^容易記住,如果沒有特別的聲明,在本文所提及的Unicode和UCS就是一個(gè)意思。Unicode的目標(biāo)是建立一套可以包含人類所有語言文 字符號你想得到想不到的各種東西的編碼,其編碼容量甚至預(yù)留了火星語以及銀河系以外語言的空間——開個(gè)玩笑,反正簡單的說,Unicode編碼集足夠的 大,如果用計(jì)算機(jī)單位來表示,其數(shù)量比3個(gè)字節(jié)大一些,不到4個(gè)字節(jié)。
Unicode和UTF
因?yàn)閁nicode包含的內(nèi)容太多,其編碼在計(jì)算機(jī)中的表示方法就成為了一個(gè)有必要研究的問題。傳統(tǒng)編碼,比如標(biāo)準(zhǔn)的7位ASCII,在計(jì)算機(jī)中的表示方 法就是占一個(gè)字節(jié)的后7位,這似乎是不需要解釋就符合大家習(xí)慣的表示方法。但是當(dāng)今Unicode的總數(shù)達(dá)到32位(計(jì)算機(jī)的最小單位是字節(jié),所以大于3 字節(jié),就只能至少用4字節(jié)表示),對于大部分常用字符,比如Unicode編碼只占一個(gè)字節(jié)大小的英語字母,占兩個(gè)字節(jié)大小漢字,都用4個(gè)字節(jié)來儲存太奢 侈了。另外,如果都用4字節(jié)直接表示,就不可避免的出現(xiàn)為0的字節(jié)。而我們知道,在C語言中,0×00的字節(jié)就是’\0′,表示的是一個(gè)字符串(char 字符串,非wchar_t)的結(jié)束,換句話說,C風(fēng)格的char字符串無法表示Unicode。
因?yàn)轭愃频姆N種問題,為Unicode在計(jì)算機(jī)中的編碼方法出現(xiàn)了,這就是UTF;所對應(yīng)的,為UCS編碼實(shí)現(xiàn)的方式也有自己的說法。一般來說,UTF- x,x表示這套編碼一個(gè)單位至少占用x位,因?yàn)閁nicode最長達(dá)到32位,所以UTF-x通常是變長的——除了UTF-32;而UCS-y表示一個(gè)單 位就占用y個(gè)字節(jié),所以能表示當(dāng)今Unicode的UCS-y只有UCS-4,但是因?yàn)闅v史的原因,當(dāng)Unicode還沒那么龐大的時(shí)候,2個(gè)字節(jié)足夠表 示,所以有UCS-2,現(xiàn)在看來,UCS-2所能表示的Unicode只是當(dāng)今Unicode的一個(gè)子集。
也就是說,如果某種編碼,能根據(jù)一定的規(guī)則算法,得到Unicode編碼,那么這種編碼方式就可以稱之為UTF。
UTF-8和Windows GB2312
UTF-8是一套“聰明”的編碼,可能用1,2,3,4個(gè)字節(jié)表示。通過UTF-8的算法,每一個(gè)字節(jié)表示的信息都很明確:這是不是某個(gè)Unicode編 碼的第一個(gè)字節(jié);如果是第一個(gè)字節(jié),這是一個(gè)幾位Unicode編碼。這種“聰明”被稱為UTF-8的自我同步,也是UTF-8成為網(wǎng)絡(luò)傳輸標(biāo)準(zhǔn)編碼的原 因。
另外,UTF-8也不會(huì)出現(xiàn)0字節(jié),所以可以表示為char字符串,所以可以成為系統(tǒng)的編碼。Linux系統(tǒng)默認(rèn)使用UTF-8編碼。
Windows GB2312一般自稱為GB2312,其實(shí)真正的名字應(yīng)該是Windows Codepage 936,這也是一種變長的編碼:1個(gè)字節(jié)表示傳統(tǒng)的ASCII部分;漢字部分是兩個(gè)字節(jié)的GBK(國標(biāo)擴(kuò)(展),拼音聲母)。Codepage 936也可以表示為char字符串,是中文Windows系統(tǒng)的默認(rèn)編碼。
我們在第1節(jié)中看到的
const char* s = “中文abc”;
在Windows中的編碼就是Codepage 936;在Linux中的編碼就是UTF-8。
需要注意的是,Codepage 936不像UTF,跟Unicode沒有換算的關(guān)系,所以只能通過“代碼頁”技術(shù)查表對應(yīng)。
UTF-16和UCS-2
UTF-16用2個(gè)字節(jié)或者4個(gè)字節(jié)表示。在2個(gè)字節(jié)大小的時(shí)候,跟UCS-2是一樣的。UTF-16不像UTF-8,沒有自我同步機(jī)制,所以,編碼大位 在前還是小位在前,就成了見仁見智的問題。我們在第1節(jié)中,“中”的UCS-2BE(因?yàn)槭莾蓚€(gè)字節(jié),所以也就是UTF-16BE)編碼是0x4E2D, 這里的BE就是大位在后的意思(也就是小位在前了),對應(yīng)的,如果是UCS-2LE,編碼就成了0x2D4E。
Windows中的wchar_t就是采用UCS-2BE編碼。需要指出的是,C++標(biāo)準(zhǔn)中對wchar_t的要求是要能表示所有系統(tǒng)能識別的字符。Windows自稱支持Unicode,但是其wchar_t卻不能表示所有的Unicode,由此違背了C++標(biāo)準(zhǔn)。
UTF-32和UCS-4
UTF-32在目前階段等價(jià)于UCS-4,都用定長的4個(gè)字節(jié)表示。UTF-32同樣存在BE和LE的問題。Linux的wchar_t編碼就是UTF- 32BE。在16位以內(nèi)的時(shí)候,UTF-32BE的后兩位(前兩位是0×00 0×00)等價(jià)于UTF-16BE也就等價(jià)于UCS-2BE
BOM
為了說明一個(gè)文件采用的是什么編碼,在文件最開始的部分,可以有BOM,比如0xFE 0xFF表示UTF-16BE,0xFF 0xFE 0×00 0×00表示UTF-32LE。UTF-8原本是不需要BOM的,因?yàn)槠渥晕彝降奶匦裕菫榱嗣鞔_說明這是UTF-8(而不是讓文本編輯器去猜),也 可以加上UTF-8的BOM:0xEF 0xBB 0xBF
以上內(nèi)容都講述得很概略,詳細(xì)信息請查閱維基百科相關(guān)內(nèi)容。

std::locale
通過前面兩節(jié)的知識,我們知道了在C/C++中,字符(串)和寬字符(串)之間的轉(zhuǎn)換不是簡單的,固定的數(shù)學(xué)關(guān)系,寬窄轉(zhuǎn)換依賴于本地化策略集 (locale)。換句話說,一個(gè)程序在運(yùn)行之前并不知道系統(tǒng)的本地化策略集是什么,程序只有在運(yùn)行之后才通過locale獲得當(dāng)時(shí)的本地化策略集。
C有自己的locale函數(shù),我們這里直接介紹C++的locale類。
先討論locale的構(gòu)造函數(shù):
locale() throw();
這個(gè)構(gòu)造函數(shù)是獲得當(dāng)前程序的locale,用法如下:
std::locale app_loc = std::locale();
或者(這是構(gòu)造對象的兩種表示方式,后同)
std::locale app_loc;
另外一個(gè)構(gòu)造函數(shù)是:
explicit locale(const char* name);
這個(gè)構(gòu)造函數(shù)以name的名字創(chuàng)建新的locale。重要的locale對象有:
std::locale sys_loc(“”);      //獲得當(dāng)前系統(tǒng)環(huán)境的locale
std::locale C_loc(“C”);      或者      std::locale C_loc = std::locale::classic();      //獲得C定義locale
std::locale old_loc = std::locale::global(new_loc);      //將new_loc設(shè)置為當(dāng)前全局locale,并將原來的locale返回給old_loc
除了這些,其它的name具體名字依賴于C++編譯器和操作系統(tǒng),比如Linux下gcc中文系統(tǒng)的locale名字為”zh_CN.UTF-8″,中文Windows可以用”chs”(更加完整的名字可以用name()函數(shù)查看)。
mbstowcs()和wcstombs()
這兩個(gè)C運(yùn)行時(shí)庫函數(shù)依賴于全局locale進(jìn)行轉(zhuǎn)換,所以,使用前必須先設(shè)置全局locale。
std::locale已經(jīng)包含在<iostream>中了,再加上我們需要用到的C++字符串,所以包含<string>。
我們先看窄到寬的轉(zhuǎn)換函數(shù):

  1. const std::wstring s2ws(const std::string& s)   
  2. {   
  3.     std::locale old_loc = std::locale::global(std::locale(""));   
  4.     const char* src_str = s.c_str();   
  5.     const size_t buffer_size = s.size() + 1;   
  6.     wchar_t* dst_wstr = new wchar_t[buffer_size];   
  7.     wmemset(dst_wstr, 0, buffer_size);   
  8.     mbstowcs(dst_wstr, src_str, buffer_size);   
  9.     std::wstring result = dst_wstr;   
  10.     delete []dst_wstr;   
  11.     std::locale::global(old_loc);   
  12.     return result;   
  13. }  
  14.  

我們將全局locale設(shè)置為系統(tǒng)locale,并保存原來的全局locale在old_loc中。
在制定轉(zhuǎn)換空間緩存大小的時(shí)候,考慮如下:char是用1個(gè)或多個(gè)對象,也就是1個(gè)或者多個(gè)字節(jié)來表示各種符號:比如,GB2312用1個(gè)字節(jié)表示數(shù)字和 字母,2個(gè)字節(jié)表示漢字;UTF-8用一個(gè)字節(jié)表示數(shù)字和字母,3個(gè)字節(jié)表示漢字,4個(gè)字節(jié)表示一些很少用到的符號,比如音樂中G大調(diào)符號等。 wchar_t是用1個(gè)對象(2字節(jié)或者4字節(jié))來表示各種符號。因此,表示同樣的字符串,寬字符串的大小(也就是wchar_t對象的數(shù)量)總是小于或 者等于窄字符串大小(char對象數(shù)量)的。+1是為了在最后預(yù)留一個(gè)值為0的對象,以便讓C風(fēng)格的char或者wchar_t字符串自動(dòng)截?cái)?#8212;—這當(dāng)然 是寬串大小等于窄串大小的時(shí)候才會(huì)用上的,大部分時(shí)候,字符串早在前面某個(gè)轉(zhuǎn)換完畢的位置就被0值對象所截?cái)嗔恕?br /> 最后我們將全局locale設(shè)置回原來的old_loc。
窄串到寬串的轉(zhuǎn)換函數(shù):

  1. const std::string ws2s(const std::wstring& ws)   
  2. {   
  3.     std::locale old_loc = std::locale::global(std::locale(""));   
  4.     const wchar_t* src_wstr = ws.c_str();   
  5.     size_t buffer_size = ws.size() * 4 + 1;   
  6.     char* dst_str = new char[buffer_size];   
  7.     memset(dst_str, 0, buffer_size);   
  8.     wcstombs(dst_str ,src_wstr, buffer_size);   
  9.     std::string result = dst_str;   
  10.     delete []dst_str;   
  11.     std::locale::global(old_loc);   
  12.     return result;   
  13. }  
  14.  

這里考慮轉(zhuǎn)換空間緩存大小的策略正好相反,在最極端的情況下,所有的wchar_t都需要4個(gè)char來表示,所以最大的可能就是4倍加1。
這兩個(gè)函數(shù)在VC和gcc中都能正常運(yùn)行(MinGW因?yàn)榍懊嬲f到的原因不支持寬字符的正常使用),在VC中會(huì)給出不安全的警告,這是告訴給那些弄不清寬窄轉(zhuǎn)換實(shí)質(zhì)的人的警告,對于了解到目前這些知識的你我來說,這就是啰嗦了。

locale和facet
C++的locale框架比C更完備。C++除了一個(gè)籠統(tǒng)本地策略集locale,還可以為locale指定具體的策略facet,甚至可以用自己定義的 facet去改造一個(gè)現(xiàn)有的locale產(chǎn)生一個(gè)新的locale。如果有一個(gè)facet類NewFacet需要添加到某個(gè)old_loc中形成新 new_loc,需要另外一個(gè)構(gòu)造函數(shù),通常的做法是:
std::locale new_loc(old_loc, new NewFacet);
標(biāo)準(zhǔn)庫里的標(biāo)準(zhǔn)facet都具有自己特有的功能,訪問一個(gè)locale對象中特定的facet需要使用模板函數(shù)use_facet:
template <class Facet> const Facet& use_factet(const locale&);
換一種說法,use_facet把一個(gè)facet類實(shí)例化成了對象,由此就可以使用這個(gè)facet對象的成員函數(shù)。
codecvt
codecvt就是一個(gè)標(biāo)準(zhǔn)facet。在C++的設(shè)計(jì)框架里,這是一個(gè)通用的代碼轉(zhuǎn)換模板——也就是說,并不是僅僅為寬窄轉(zhuǎn)換制定的。
templat <class I, class E, class State> class std::codecvt: public locale, public codecvt_base{…};
I表示內(nèi)部編碼,E表示外部編碼,State是不同轉(zhuǎn)換方式的標(biāo)識,如果定義如下類型:
typedef std::codecvt<wchar_t, char, mbstate_t> CodecvtFacet;
那么CodecvtFacet就是一個(gè)標(biāo)準(zhǔn)的寬窄轉(zhuǎn)換facet,其中mbstate_t是標(biāo)準(zhǔn)寬窄轉(zhuǎn)換的State。
內(nèi)部編碼和外部編碼
我們考慮第1節(jié)中提到的C++編譯器讀取源文件時(shí)候的情形,當(dāng)讀到L”中文abc”的時(shí)候,外部編碼,也就是源文件的編碼,是GB2312或者UTF-8 的char,而編譯器必須將其翻譯為UCS-2BE或者UTF-32BE的wchar_t,這也就是程序的內(nèi)部編碼。如果不是寬字符串,內(nèi)外編碼都是 char,也就不需要轉(zhuǎn)換了。類似的,當(dāng)C++讀寫文件的時(shí)候 ,就會(huì)可能需要到內(nèi)外編碼轉(zhuǎn)換。事實(shí)上,codecvt就正是被文件流緩存basic_filebuf所使用的。理解這一點(diǎn)很重要,原因會(huì)在下一小節(jié)看 到。
CodecvtFacet的in()和out()
因?yàn)樵贑odecvtFacet中,內(nèi)部編碼設(shè)置為wchar_t,外部編碼設(shè)置為char,轉(zhuǎn)換模式是標(biāo)準(zhǔn)寬窄轉(zhuǎn)換mbstate_t,所以,類方法 in()就是從char標(biāo)準(zhǔn)轉(zhuǎn)換到wchar_t,out()就是從wchar_t標(biāo)準(zhǔn)轉(zhuǎn)換到char。這就成了我們正需要的內(nèi)外轉(zhuǎn)換函數(shù)。
result in(State& s, const E* from, const E* from_end, const E*& from_next, I* to,  I* to_end, I*& to_next) const;
result out(State& s, const I* from, const I* from_end, const I*& from_next, E* to, E* to_end, E*& to_next) const;
其中,s是非const引用,保存著轉(zhuǎn)換位移狀態(tài)信息。這里需要重點(diǎn)強(qiáng)調(diào)的是,因?yàn)檗D(zhuǎn)換的實(shí)際工作交給了運(yùn)行時(shí)庫,也就是說,轉(zhuǎn)換可能不是在程序的主進(jìn)程 中完成的,而轉(zhuǎn)換工作依賴于查詢s的值,因此,如果s在轉(zhuǎn)換結(jié)束前析構(gòu),就可能拋出運(yùn)行時(shí)異常。所以,最安全的辦法是,將s設(shè)置為全局變量!
const的3個(gè)指針分別是待轉(zhuǎn)換字符串的起點(diǎn),終點(diǎn),和出現(xiàn)錯(cuò)誤時(shí)候的停點(diǎn)(的下一個(gè)位置);另外3個(gè)指針是轉(zhuǎn)換目標(biāo)字符串的起點(diǎn),終點(diǎn)以及出現(xiàn)錯(cuò)誤時(shí)候的停點(diǎn)(的下一個(gè)位置)。
代碼如下:
頭文件:

  1. //Filename string_wstring_cppcvt.hpp   
  2. #ifndef STRING_WSTRING_CPPCVT_HPP   
  3. #define STRING_WSTRING_CPPCVT_HPP   
  4. #include <iostream>   
  5. #include <string>   
  6. const std::wstring s2ws(const std::string& s);   
  7. const std::string ws2s(const std::wstring& s);   
  8. #endif  
  9.  

實(shí)現(xiàn):

  1. #include "string_wstring_cppcvt.hpp"   
  2. mbstate_t in_cvt_state;   
  3. mbstate_t out_cvt_state;   
  4. const std::wstring s2ws(const std::string& s)   
  5. {   
  6.     std::locale sys_loc("");   
  7.     const char* src_str = s.c_str();   
  8.     const size_t BUFFER_SIZE = s.size() + 1;   
  9.     wchar_t* intern_buffer = new wchar_t[BUFFER_SIZE];   
  10.     wmemset(intern_buffer, 0, BUFFER_SIZE);   
  11.     const char* extern_from = src_str;   
  12.     const char* extern_from_end = extern_from + s.size();   
  13.     const char* extern_from_next = 0;   
  14.     wchar_t* intern_to = intern_buffer;   
  15.     wchar_t* intern_to_end = intern_to + BUFFER_SIZE;   
  16.     wchar_t* intern_to_next = 0;   
  17.     typedef std::codecvt<wchar_t, char, mbstate_t> CodecvtFacet;   
  18.     CodecvtFacet::result cvt_rst =   
  19.     std::use_facet<CodecvtFacet>(sys_loc).in(   
  20.             in_cvt_state,   
  21.             extern_from, extern_from_end, extern_from_next,   
  22.             intern_to, intern_to_end, intern_to_next);   
  23.     if (cvt_rst != CodecvtFacet::ok) {   
  24.         switch(cvt_rst) {   
  25.         case CodecvtFacet::partial:   
  26.                 std::cerr << "partial";   
  27.                 break;   
  28.         case CodecvtFacet::error:   
  29.                 std::cerr << "error";   
  30.                 break;   
  31.         case CodecvtFacet::noconv:   
  32.                 std::cerr << "noconv";   
  33.                 break;   
  34.         default:   
  35.                 std::cerr << "unknown";   
  36.         }   
  37.         std::cerr  << ", please check in_cvt_state."   
  38. << std::endl;   
  39.     }   
  40.     std::wstring result = intern_buffer;   
  41.     delete []intern_buffer;   
  42.     return result;   
  43. }   
  44. const std::string ws2s(const std::wstring& ws)   
  45. {   
  46.     std::locale sys_loc("");   
  47.     const wchar_t* src_wstr = ws.c_str();   
  48.     const size_t MAX_UNICODE_BYTES = 4;   
  49.     const size_t BUFFER_SIZE =   
  50.                 ws.size() * MAX_UNICODE_BYTES + 1;   
  51.     char* extern_buffer = new char[BUFFER_SIZE];   
  52.     memset(extern_buffer, 0, BUFFER_SIZE);   
  53.     const wchar_t* intern_from = src_wstr;   
  54.     const wchar_t* intern_from_end = intern_from + ws.size();   
  55.     const wchar_t* intern_from_next = 0;   
  56.     char* extern_to = extern_buffer;   
  57.     char* extern_to_end = extern_to + BUFFER_SIZE;   
  58.     char* extern_to_next = 0;   
  59.     typedef std::codecvt&lt;wchar_t, char, mbstate_t> CodecvtFacet;   
  60.     CodecvtFacet::result cvt_rst =   
  61.     std::use_facet<CodecvtFacet>(sys_loc).out(   
  62.             out_cvt_state,   
  63.             intern_from, intern_from_end, intern_from_next,   
  64.             extern_to, extern_to_end, extern_to_next);   
  65.     if (cvt_rst != CodecvtFacet::ok) {   
  66.         switch(cvt_rst) {   
  67.         case CodecvtFacet::partial:   
  68.                 std::cerr << "partial";   
  69.                 break;   
  70.         case CodecvtFacet::error:   
  71.                 std::cerr << "error";   
  72.                 break;   
  73.         case CodecvtFacet::noconv:   
  74.                 std::cerr << "noconv";   
  75.                 break;   
  76.         default:   
  77.                 std::cerr << "unknown";   
  78.         }   
  79.         std::cerr << ", please check out_cvt_state."   
  80. << std::endl;   
  81.     }   
  82.     std::string result = extern_buffer;   
  83.     delete []extern_buffer;   
  84.     return result;   
  85. }  
  86.  

最后補(bǔ)充說明一下std::use_facet&lt;CodecvtFacet>(sys_loc).in()和 std::use_facet<CodecvtFacet>(sys_loc).out()。sys_loc是系統(tǒng)的locale,這個(gè) locale中就包含著特定的codecvt facet,我們已經(jīng)typedef為了CodecvtFacet。用use_facet對CodecvtFacet進(jìn)行了實(shí)例化,所以可以使用這個(gè) facet的方法in()和out()。

C++的流和本地化策略集
BS在設(shè)計(jì)C++流的時(shí)候希望其具備智能化,并且是可擴(kuò)展的智能化,也就是說,C++的流可以“讀懂”一些內(nèi)容。比如:

std::cout <&lt; 123 &lt;&lt; “ok” &lt;&lt; std::endl;

這句代碼中,std::cout是能判斷出123是int而”ok”是const char[3]。利用流的智能,甚至可以做一些基礎(chǔ)類型的轉(zhuǎn)換,比如從int到string,string到int:

  1. std::string str("123");   
  2. std::stringstream sstr(str);   
  3. int i;   
  4. sstr >&gt; i;  
  5.  
  6. int i = 123;   
  7. std::stringstream sstr;   
  8. sstr <&lt; i;   
  9. std::string str = sstr.str();  
  10.  

盡管如此,C++并不滿足,C++甚至希望流能“明白”時(shí)間,貨幣的表示法。而時(shí)間和貨幣的表示方法在世界范圍內(nèi)是不同的,所以,每一個(gè)流都有自己 的locale在影響其行為,C++中叫做激活(imbue,也有翻譯成浸染)。而我們知道,每一個(gè)locale都有多個(gè)facet,這些facet并非 總是被use_facet使用的。決定使用哪些facet的,是流的緩存basic_streambuf及其派生類basic_stringbuf和 basic_filebuf。我們要用到的facet是codecvt,這個(gè)facet只被basic_filebuf使用——這就是為什么只能用 fstream來實(shí)現(xiàn)寬窄轉(zhuǎn)換,而無法使用sstream來實(shí)現(xiàn)的原因。
頭文件:

  1. //filename string_wstring_fstream.hpp   
  2. #ifndef STRING_WSTRING_FSTREAM_HPP   
  3. #define STRING_WSTRING_FSTREAM_HPP   
  4. #include &lt;string>   
  5. const std::wstring s2ws(const std::string& s);   
  6. const std::string ws2s(const std::wstring& s);   
  7. #endif 

實(shí)現(xiàn):

  1. #include <string>   
  2. #include <fstream>   
  3. #include "string_wstring_fstream.hpp"   
  4. const std::wstring s2ws(const std::string& s)   
  5. {   
  6.     std::locale sys_loc("");   
  7.     std::ofstream ofs("cvt_buf");   
  8.     ofs <&lt; s;   
  9.     ofs.close();   
  10.     std::wifstream wifs("cvt_buf");   
  11.     wifs.imbue(sys_loc);   
  12.     std::wstring wstr;   
  13.     wifs >&gt; wstr;   
  14.     wifs.close();   
  15.     return wstr;   
  16. }   
  17. const std::string ws2s(const std::wstring& s)   
  18. {   
  19.     std::locale sys_loc("");   
  20.     std::wofstream wofs("cvt_buf");   
  21.     wofs.imbue(sys_loc);   
  22.     wofs <&lt; s;   
  23.     wofs.close();   
  24.     std::ifstream ifs("cvt_buf");   
  25.     std::string str;   
  26.     ifs >&gt; str;   
  27.     ifs.close();   
  28.     return str;   
  29. }  
  30.  

在窄到寬的轉(zhuǎn)化中,我們先使用默認(rèn)的本地化策略集(locale)將s通過窄文件流ofs傳入文件,這是char到char的傳遞,沒有任何轉(zhuǎn)換; 然后我們打開寬文件流wifs,并用系統(tǒng)的本地化策略集(locale)去激活(imbue)之,流在讀回寬串wstr的時(shí)候,就是char到 wchar_t的轉(zhuǎn)換,并且因?yàn)榧せ盍藄ys_loc,所以實(shí)現(xiàn)標(biāo)準(zhǔn)窄到寬的轉(zhuǎn)換。
在寬到窄的轉(zhuǎn)化中,我們先打開的是寬文件流wofs,并且用系統(tǒng)的本地化策略集sys_loc激活(imbue)之,這時(shí)候,因?yàn)橐獙懙奈募?cvt_buf是一個(gè)外部編碼,所以執(zhí)行了從wchar_t到char的標(biāo)準(zhǔn)轉(zhuǎn)換。讀回來的文件流從char到char,不做任何轉(zhuǎn)換。

硬編碼的硬傷
我們現(xiàn)在知道,C/C++的寬窄轉(zhuǎn)換是依賴系統(tǒng)的locale的,并且在運(yùn)行時(shí)完成。考慮這樣一種情況,我們在簡體中文Windows下編譯如下語句:
const char* s = “中文abc”;
根據(jù)我們之前的討論,編譯器將按照Windows Codepage936(GB2312)對這個(gè)字符串進(jìn)行編碼。如果我們在程序中運(yùn)行寬窄轉(zhuǎn)換函數(shù),將s轉(zhuǎn)換為寬字符串ws,如果這個(gè)程序運(yùn)行在簡體中文 環(huán)境下是沒問題的,將執(zhí)行從GB2312到UCS-2BE的轉(zhuǎn)換;但是,如果在其他語言環(huán)境下,比如是繁體中文BIG5,程序?qū)⒏鶕?jù)系統(tǒng)的locale執(zhí) 行從BIG5到UCS-2BE的轉(zhuǎn)換,這顯然就出現(xiàn)了錯(cuò)誤。
補(bǔ)救
有沒有補(bǔ)救這個(gè)問題的辦法呢?一個(gè)解決方案就是執(zhí)行不依賴locale的寬窄轉(zhuǎn)換。實(shí)際上,這就已經(jīng)不是寬窄轉(zhuǎn)換之間的問題了,而是編碼之間轉(zhuǎn)換的問題 了。我們可以用GNU的libiconv實(shí)現(xiàn)任意編碼間的轉(zhuǎn)換,對于以上的具體情況,指明是從GB2312到UCS-2BE就不會(huì)出錯(cuò)。(請參考本人前面 的章節(jié):win32下的libiconv),但這顯然是一個(gè)笨拙的策略:我們在簡體中文Windows下必須使用GB2312到UCS-2BE版本的寬窄轉(zhuǎn)換函數(shù);到了BIG5環(huán)境下,就必須重新寫從BIG5到UCS-2BE的寬窄轉(zhuǎn)換函數(shù)。
Windows的策略
Windows的策略是淘汰了窄字符串,干脆只用寬字符串。所有的硬編碼全部加上特定宏,比如TEXT(),如果程序是所謂Unicode編譯,在編譯時(shí)就翻譯為UCS2-BE——Windows自稱為Unicode編程,其本質(zhì)是使用了UCS-2BE的16位寬字符串。
Linux的策略
Linux下根本就不存在這個(gè)問題!因?yàn)楦鞣N語言的Linux都使用UTF-8的編碼,所以,無論系統(tǒng)locale如何變化,窄到寬轉(zhuǎn)換的規(guī)則一直是UTF-8到UTF32-BE 。
跨平臺策略
因?yàn)樵?6位的范圍內(nèi),UTF32-BE的前16位為0,后16位與UCS2-BE是一樣的,所以,即使wchar_t的sizeof()不一樣,在一般情況下,跨平臺使用寬字符(串)也應(yīng)該是兼容的。但是依然存在潛在的問題,就是那些4字節(jié)的UTF32編碼。
gettext策略
以上都是將ASCII及以外的編碼硬編碼在程序中的辦法。GNU的gettext提供了另外一種選擇:在程序中只硬編碼ASCII,多語言支持由gettext函數(shù)庫在運(yùn)行時(shí)加載。(對gettext的介紹請參考本人前面的章節(jié):Win32下的GetText)。 gettext的多語言翻譯文件不在程序中,而是單獨(dú)的提出來放在特定的位置。gettext明確的知道這些翻譯文件的編碼,所以可以準(zhǔn)確的告訴給系統(tǒng)翻 譯的正確信息,而系統(tǒng)將這些信息以當(dāng)前的系統(tǒng)locale編碼成窄字符串反饋給程序。例如,在簡體中文Windows中,gettext的po文件也可以 以UTF-8儲存,gettext將po文件翻譯成mo文件,確保mo文件在任何系統(tǒng)和語言環(huán)境下都能夠正確翻譯。在運(yùn)行是傳給win32程序的窄串符合 當(dāng)前l(fā)ocale,是GB2312。gettext讓國際化的翻譯更加的方便,缺點(diǎn)是目前我沒找到支持寬字符串的版本(據(jù)說是有ugettext()支持 寬字符串),所以要使用gettext只能使用窄字符串。但是gettext可以轉(zhuǎn)換到寬字符串,而且不會(huì)出現(xiàn)寬窄轉(zhuǎn)換的問題,因?yàn)間ettext是運(yùn)行 時(shí)根據(jù)locale翻譯的。例如:
const char* s = gettext(“Chinese a b c”);
其中”Chinese a b c”在po中的翻譯是”中文abc”
使用依賴locale的運(yùn)行時(shí)寬窄轉(zhuǎn)換函數(shù):
const std::wstring wstr = s2ws(s);
運(yùn)行時(shí)調(diào)用該po文件對應(yīng)的mo文件,在簡體中文環(huán)境下就以GB2312傳給程序,在繁體中文中就以BIG5傳給程序,這樣s2ws()總能夠正常換算編碼。
更多
在本文的最后,我想回到C++的stream問題上。用fstream轉(zhuǎn)換如此的簡單,sstream卻不支持。改造一個(gè)支持codecvt的 string stream需要改造basic_stringbuf。basic_stringbuf和basic_filebuf都派生自 basic_streambuf,所不同的是basic_filebuf在構(gòu)造和open()的時(shí)候調(diào)用了codecvt,只需要在 basic_stringbuf中添加這個(gè)功能就可以了。說起來容易,實(shí)際上是需要重新改造一個(gè)STL模板,盡管這些模板源代碼都是在標(biāo)準(zhǔn)庫頭文件中現(xiàn)成 的,但是我還是水平有限,沒有去深究了。另外一個(gè)思路是構(gòu)建一個(gè)基于內(nèi)存映射的虛擬文件,這個(gè)框架在boost的iostreams庫中,有興趣的朋友可 以深入的研究。
(完)