C++11 Unicode編碼轉(zhuǎn)換
1.char16_t與char32_t
在C++98中,為了支持Unicode字符,使用wchar_t類型來表示“寬字符”,但并沒有嚴(yán)格規(guī)定位寬,而是讓wchar_t的寬度由編譯器實(shí)現(xiàn),因此不同的編譯器有著不同的實(shí)現(xiàn)方式,GNU C++規(guī)定wchar_t為32位,Visual C++規(guī)定為16位。由于wchar_t寬度沒有一個(gè)統(tǒng)規(guī)定,導(dǎo)致使用wchar_t的代碼在不同平臺(tái)間移植時(shí),可能出現(xiàn)問題。這一狀況在C++11中得到了一定的改善,從此Unicode字符的存儲(chǔ)有了統(tǒng)一類型:
(1)char16_t:用于存儲(chǔ)UTF-16編碼的Unicode字符。
(2)char32_t:用于存儲(chǔ)UTF-32編碼的Unicode字符。
至于UTF-8編碼的Unicode數(shù)據(jù),C++11還是使用了8bits寬度的char類型數(shù)組來表示,而char16_t和char32_t的寬度由其名稱可以看出,char16_t為16bits,char32_t為32bits。
2.定義字符串的5種方式
除了使用新類型char16_t與char32_t來表示Unicode字符,此外,C++11還新增了三種前綴來定義不同編碼的字符串,新增前綴如下:
(1)u8表示為UTF-8編碼;
(2)u表示為UTF-16編碼;
(3)U表示為UTF-32編碼。
C++98中有兩種定義字符串的方式,一是直接使用雙引號(hào)定義多字節(jié)字符串,二是通過前綴“L”表示wchar_t字符串(寬字符串)。至此,C++中共有5種定義字符串的方式。
3.影響字符串正確處理的因素
在使用不同方式定義不同編碼的字符串時(shí),我們需要注意影響字符串處理和顯示的幾個(gè)因素有編輯器、編譯器和輸出環(huán)境。
代碼編輯器采用何種編碼方式?jīng)Q定了字符串最初的編碼,比如編輯器如果采用GBK,那么代碼文件中的所有字符都是以GBK編碼存儲(chǔ)。當(dāng)編譯器處理字符串時(shí),可以通過前綴來判斷字符串的編碼類型,如果目標(biāo)編碼與原編碼不同,則編譯器會(huì)進(jìn)行轉(zhuǎn)換,比如C++11中的前綴u8表示目標(biāo)編碼為UTF-8的字符,如果代碼文件采用的是GBK,編譯器按照UTF-8去解析字符串常量,則可能會(huì)出現(xiàn)錯(cuò)誤。
//代碼文件為GBK編碼 #include <iomanip> #include <iostream> using namespace std; int main() { const char* sTest = u8"你好"; for(int i=0;sTest[i]!=0;++i) { cout<<setiosflags(ios::uppercase)<<hex<<(uint32_t)(uint8_t)sTest[i]<<" "; } return 0; } //編譯選項(xiàng):g++ -std=c++0x -finput-charset=utf-8 test.cpp
程序輸出結(jié)果:C4 E3 BA C3。這個(gè)碼值是GBK的碼值,因?yàn)椤澳恪钡腉BK碼值是0xC4E3,“好”的GBK碼值是0xBAC3??梢?,編譯器未成功地將GBK編碼的“你好”轉(zhuǎn)換為UTF-8的碼值“你”(E4 BD A0)“好”(E5 A5 BD),原因是使用編譯選項(xiàng)-finput-charset=utf-8指定代碼文件編碼為UTF-8,而實(shí)際上代碼文件編碼為GBK,導(dǎo)致編譯器出現(xiàn)錯(cuò)誤的認(rèn)知。如果使用-finput-charset=gbk,那么編譯器在編譯時(shí)會(huì)將GBK編碼的“你好”轉(zhuǎn)換為UTF-8編碼,正確輸出E4 BD A0 E5 A5 BD。
代碼編輯器和編譯器這兩個(gè)環(huán)節(jié)在處理字符串如果沒有問題,那么最后就是顯示環(huán)節(jié)。字符串的正確顯示依賴于輸出環(huán)境。C++輸出流對(duì)象cout能夠保證的是將數(shù)據(jù)以二進(jìn)制輸出到輸出設(shè)備,但輸出設(shè)備(比如Linux shell或者Windows console)是否能夠支持特定的編碼類型的輸出,則取決于輸出環(huán)境。比如Linux虛擬終端XShell,配置終端編碼類型為GBK,則無法顯示輸出的UTF-8編碼字符串。
一個(gè)字符串從定義到處理再到輸出,涉及到編輯器、編譯器和輸出環(huán)境三個(gè)因素,正確的處理和顯示需要三個(gè)因素的共同保障,每一個(gè)環(huán)節(jié)都不能出錯(cuò)。一個(gè)字符串的處理流程與因素如下圖所示:
當(dāng)然如果想避開編輯器編碼對(duì)字符串的影響,可以使用Unicode碼值來定義字符串常量,參看如下代碼:
//代碼文件為GBK編碼 #include <iomanip> #include <iostream> using namespace std; int main() { const char* sTest = u8"\u4F60\u597D"; //你好的Uunicode碼值分別是:0x4F60和0x597D for(int i=0;sTest[i]!=0;++i) { cout<<setiosflags(ios::uppercase)<<hex<<(uint32_t)(uint8_t)sTest[i]<<" "; } return 0; } //編譯選項(xiàng):g++ -std=c++0x -finput-charset=utf-8 test.cpp
程序輸出結(jié)果:E4 BD A0 E5 A5 BD??梢?,即使編譯器對(duì)代碼文件的編碼理解有誤,仍然可以正確地以UTF-8編碼輸出“你好”的碼值。原因是ASCII字符使用GBK與UTF-8編碼碼值是相同的,所以直接書寫Unicode碼值來表示字符串是一種比較保險(xiǎn)的做法,缺點(diǎn)就是難以閱讀。
4.Unicode的庫支持
C++11在標(biāo)準(zhǔn)庫中增加了一些Unicode編碼轉(zhuǎn)換的函數(shù),開發(fā)人員可以使用庫中的一些新增編碼轉(zhuǎn)換函數(shù)來完成各種Unicode編碼間的轉(zhuǎn)換,函數(shù)原型如下:
//多字節(jié)字符轉(zhuǎn)換為UTF-16編碼 size_t mbrtoc16 ( char16_t * pc16, const char * pmb, size_t max, mbstate_t * ps); //UTF-16字符轉(zhuǎn)換為多字節(jié)字符 size_t c16rtomb ( char * pmb, char16_t c16, mbstate_t * ps ); //多字節(jié)字符轉(zhuǎn)換為UTF-32編碼 size_t mbrtoc32 ( char32_t * pc32, const char * pmb, size_t max, mbstate_t * ps); //UTF-32字符轉(zhuǎn)換為多字節(jié)字符 size_t c32rtomb ( char * pmb, char32_t c32, mbstate_t * ps );
函數(shù)名稱中mb表示multi-byte(多字節(jié)),rto表示convert to(轉(zhuǎn)換為),c16表示char16_t,了解這些,可以根據(jù)函數(shù)名稱直觀的理解它們的作用。下面給一下UTF-16字符串轉(zhuǎn)換為多字節(jié)字符串(以GBK為例)的例子:
#include <uchar.h> #include <string.h> #include <locale> #include <iomanip> #include <iostream> using namespace std; int main() { const char16_t* utf16 = u"\u4F60\u597D\u554A"; size_t utf16Len=char_traits<char16_t>::length(utf16); char* gbk =new char[utf16Len*2+1]; memset(gbk,0, utf16Len * 2 + 1); char* pGbk = gbk; setlocale(LC_ALL, "zh_CN.gbk"); mbstate_t mbs; //轉(zhuǎn)換狀態(tài) size_t length = 0; while (*utf16) { pGbk += length; length = c16rtomb(pGbk, *utf16, &mbs); if (length == 0 || pGbk - gbk>sizeof(gbk)) { cout << "failed" << endl; break; //轉(zhuǎn)換失敗 } ++utf16; } for (int i = 0; gbk[i] != 0; ++i) { cout << setiosflags(ios::uppercase) << hex << (uint32_t)(uint8_t)gbk[i] << " "; } return 0; } //編譯選項(xiàng):g++ -std=c++0x test.cpp
程序輸出結(jié)果:C4 E3 BA C3 B0 A1??梢?,使用c16rtomb()完成了將“你好啊”從UTF-16編碼到多字節(jié)編碼(GBK)的轉(zhuǎn)換。上面的轉(zhuǎn)換,我們用到了locale機(jī)制。locale表示的是一個(gè)地域的特征,包括字符編碼、數(shù)字時(shí)間表示形式、貨幣符號(hào)等。locale串使用“zh_CN.gbk”表示目的多字節(jié)字符串使用GBK編碼。
上面通過Unicode字符的轉(zhuǎn)換來完成字符串的轉(zhuǎn)換,實(shí)際上C++提供了一個(gè)類模板codecvt用于完成Unicode字符串與多字節(jié)字符串之間的轉(zhuǎn)換,主要分為4種:
codecvt<char,char,mbstate_t> //performs no conversion codecvt<wchar_t,char,mbstate_t> //converts between native wide and narrow character sets codecvt<char16_t,char,mbstate_t> //converts between UTF16 and UTF8 encodings, since C++11 codecvt<char32_t,char,mbstate_t> //converts between UTF32 and UTF8 encodings,since C++11
上面的codecvt實(shí)際上是locale的一個(gè)facet,facet可以簡(jiǎn)單地理解為locale的一些接口。通過codecvt,可以完成當(dāng)前l(fā)ocale下多字節(jié)編碼字符串與Unicode字符間的轉(zhuǎn)換,也包括Unicode字符編碼間的轉(zhuǎn)換。這里的多字節(jié)字符串不僅可以試UTF-8,也可以是GBK或者其它編碼,實(shí)際依賴于locale所采用的編碼方式。每種codecvt負(fù)責(zé)不同類型編碼的轉(zhuǎn)換,但是目前編譯器的支持情況并沒有那么完整,一種locale并不一定支持所有的codecvt,程序員可以通過has_facet函數(shù)模板來查詢指定locale下的支持情況。參考代碼如下:
#include <locale> #include <iostream> using namespace std; int main() { //定義一個(gè)locale并查詢?cè)搇ocale是否支持一些facet locale lc("zh_CN.gbk"); bool can_cvt = has_facet<codecvt<char, char, mbstate_t>>(lc); if (!can_cvt) cout<<"do not support char-char facet"<<endl; can_cvt = has_facet<codecvt<wchar_t, char, mbstate_t>>(lc); if (!can_cvt) cout << "do not support wchar_t-char facet" << endl; can_cvt = has_facet<codecvt<char16_t, char, mbstate_t>>(lc); if (!can_cvt) cout << "do not support char16_t-char facet" << endl; can_cvt = has_facet<codecvt<char32_t, char, mbstate_t>>(lc); if (!can_cvt) cout << "do not support char32_t-char facet" << endl; } //編譯選項(xiàng):g++ -std=c++11 test.cpp //g++版本:gcc version 4.8.5 20150623 (Red Hat 4.8.5-4) (GCC)
程序輸出結(jié)果:
do not support char16_t-char facet
do not support char32_t-char facet
由此可見,從char到char16_t與char32_t轉(zhuǎn)換的兩種facet還沒有被實(shí)驗(yàn)機(jī)使用的編譯器支持。
假如實(shí)驗(yàn)機(jī)支持從char與char16_t的轉(zhuǎn)換,可參考如下代碼:
#include <uchar.h> #include <string.h> #include <locale> #include <iomanip> #include <iostream> using namespace std; int main() { typedef std::codecvt<char16_t,char,std::mbstate_t> facet_type; std::locale mylocale("zh_CN.gbk"); try { const facet_type& myfacet = std::use_facet<facet_type>(mylocale); const char16_t* utf16 = u"\u4F60\u597D\u554A"; //你好啊 size_t utf16Len = char_traits<char16_t>::length(utf16); cout<< utf16Len <<endl; char* gbk = new char[utf16Len*2+1]; memset(gbk, 0, utf16Len * 2 + 1); std::mbstate_t mystate; //轉(zhuǎn)換狀態(tài) const char16_t* pwc; //from_next char* pc; //to_next facet_type::result myresult = myfacet.out(mystate,utf16,utf16+utf16Len+1,pwc, gbk, gbk + utf16Len * 2+1, pc); if (myresult == facet_type::ok) { std::cout << "Translation successful:" << endl; } for (int i = 0; gbk[i] != 0; ++i) { cout << setiosflags(ios::uppercase) << hex << (uint32_t)(uint8_t)gbk[i] << " "; } delete[] gbk; } catch(...) { cout<<"do not support char16_t-char facet"<<endl; return -1; } return 0; }
由于實(shí)驗(yàn)環(huán)境并不支持char與char16_t相互轉(zhuǎn)換的facet,所以程序輸出結(jié)果為:do not support char16_t-char facet。
5.u16string與u32string
C++11新增了UTF-16和UTF-32編碼的字符類型char16_t和char32_t,當(dāng)然少不了對(duì)應(yīng)的字符串類型,分別是u16string與與u32string,二者的存在類似與string與wstring。四者的定義如下:
typedef basic_string<char> string; typedef basic_string<wchar_t> wstring; typedef basic_string<char16_t> u16string; typedef basic_string<char32_t> u32string;
我們對(duì)string與wstring應(yīng)該比較熟悉,對(duì)于u16string與u32string在用法上是差不多了,有相同的成員接口與類型,只需要記住其存儲(chǔ)的字符編碼類型不同即可。下面看一下u16string使用的簡(jiǎn)單示例。
#include <iomanip> #include <iostream> using namespace std; int main() { u16string u16str = u"\u4F60\u597D\u554A"; //你好啊 cout << u16str.length() << endl; //字符數(shù) for (int i = 0; i<u16str.length(); ++i) { cout << setiosflags(ios::uppercase) << hex << (uint16_t)u16str[i] << " "; } }
程序輸出:
3
4F60 597D 554A。
以上就是C++11 Unicode編碼轉(zhuǎn)換的詳細(xì)內(nèi)容,更多關(guān)于C++11 Unicode編碼轉(zhuǎn)換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++函數(shù)參數(shù)取默認(rèn)值的深入詳解
本篇文章是對(duì)C++中函數(shù)參數(shù)取默認(rèn)值進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C語言實(shí)現(xiàn)的一個(gè)三子棋游戲詳解流程
三子棋是一種民間傳統(tǒng)游戲,又叫九宮棋、圈圈叉叉、一條龍、井字棋等。將正方形對(duì)角線連起來,相對(duì)兩邊依次擺上三個(gè)雙方棋子,只要將自己的三個(gè)棋子走成一條線,對(duì)方就算輸了2021-10-10