UnicodeUnicode是計算機領域的一項行業(yè)標準,它對世界上絕大部分的文字的進行整理和統(tǒng)一編碼,Unicode的編碼空間可以劃分為17個平面(plane),每個平面包含2的16次方(65536)個碼位。17個平面的碼位可表示為從U+0000到U+10FFFF,共計1114112個碼位,第一個平面稱為基本多語言平面(Basic Multilingual Plane, BMP),或稱第零平面(Plane 0)。其他平面稱為輔助平面(Supplementary Planes)。基本多語言平面內,從U+D800到U+DFFF之間的碼位區(qū)段是永久保留不映射到Unicode字符,所以有效碼位為1112064個。最新的版本是Unicode 6.3發(fā)布于2013年9月30日。
Unicode的編碼方式對于被Unicode收錄的字符其編碼是唯一且確定的。但是Unicode的實現(xiàn)方式(出于傳輸、存儲、處理或向后兼容的考慮)卻有不同的幾種,其中最流行的是UTF-8、UTF-16、UCS2、UCS4/UTF-32等,細分的話還有大小端的區(qū)別。
UTF-8(8-bit Unicode Transformation Format)UTF-8是一種變長編碼,對于一個Unicode的字符被編碼成1至4個字節(jié)。Unicode編碼與UTF-8的編碼的對應關系如下表
Unicode編碼 |
UTF-8編碼(二進制) |
U+0000 – U+007F |
0xxxxxxx |
U+0080 – U+07FF |
110xxxxx 10xxxxxx |
U+0800 – U+FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
U+10000 – U+10FFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
其中絕大部分的中文用三個字節(jié)編碼,部分中文用四個字節(jié)編碼,舉例如下:
Unicode |
字符 |
UTF-8編碼 |
U+0041 |
A |
0x41 |
U+7834 |
破 |
0xE7 0xA0 0xB4 |
U+6653 |
曉 |
0xE6 0x99 0x93 |
U+2A6A5 |
??(四個龍) |
0xF0 0xAA 0x9A 0xA5 |
優(yōu)點:
- 向后兼容ASCII編碼;
- 沒有字節(jié)序(大小端)的問題適合網(wǎng)絡傳輸;
- 存儲英文和拉丁文等字符非常節(jié)省存儲空間。
缺點:
- 變長編碼不利于文本處理;
- 對于CJK等文字比較浪費存儲空間。
UTF-16(16-bit Unicode Transformation Format)UTF-16也是一種變長編碼,對于一個Unicode字符被編碼成1至2個碼元,每個碼元為16位。
基本多語言平面(碼位范圍U+0000-U+FFFF)在基本多語言平面內的碼位UTF-16編碼使用1個碼元且其值與Unicode是相等的(不需要轉換)。舉例如下
Unicode |
字符 |
UTF-16(碼元) |
UTF-16 LE(字節(jié)) |
UTF-16 BE(字節(jié)) |
U+0041 |
A |
0x0041 |
0x41 0x00 |
0x00 0x41 |
U+7834 |
破 |
0x7834 |
0x34 0x78 |
0x78 0x34 |
U+6653 |
曉 |
0x6653 |
0x53 0x66 |
0x66 0x53 |
輔助平面(碼位范圍U+10000-U+10FFFF)在輔助平面內的碼位在UTF-16中被編碼為一對16bit的碼元(即32bit,4字節(jié)),稱作代理對(surrogate pair)。組成代理對的兩個碼元前一個稱為前導代理(lead surrogates)范圍為0xD800-0xDBFF,后一個稱為后尾代理(trail surrogates)范圍為0xDC00-0xDFFF。UTF-16輔助平面代理對與Unicode的對應關系如下表
Lead \ Trail |
0xDC00 |
0xDC01 |
… |
0xDFFF |
0xD800 |
U+10000 |
U+10001 |
… |
U+103FF |
0xD801 |
U+10400 |
U+10401 |
… |
U+107FF |
? |
? |
? |
? |
? |
0xDBFF |
U+10FC00 |
U+10FC01 |
… |
U+10FFFF |
舉例如下
Unicode |
字符 |
UTF-16(碼元) |
UTF-16 LE(字節(jié)) |
UTF-16 BE(字節(jié)) |
U+2A6A5 |
?? |
0xD869 0xDEA5 |
0x69 0xD8 0xA5 0xDE |
0xD8 0x69 0xDE 0xA5 |
優(yōu)點:
- 絕大部分的文字都可以用兩個字節(jié)編碼,對于CJK文字是比較節(jié)省空間的;
- 文本處理比UTF-8方便得多。
缺點:
- 存儲和傳輸需要考慮字節(jié)序的問題;
- 不兼容ASCII。
UCS2(2-byte Universal Character Set)UCS2是一種定長編碼,編碼范圍為0x0000-0xFFFF。在基本多語言平面內與UTF-16是等價。UCS2沒有類似于UTF-16中代理對的概念,所以對于0xD869 0xDEA5會識別成兩個字符。因為是定長編碼,所以文本處理很方便。缺點是不能表示全部的Unicode字符。
UCS4(4-byte Universal Character Set)/UTF-32(32-bit Unicode Transformation Format)UCS4/UTF-32是一種定長編碼,使用1個32bit的碼元,其值與Unicode編碼值相等。舉例如下:
Unicode |
字符 |
UTF-32(碼元) |
UTF-32 LE(字節(jié)) |
UTF-32 BE(字節(jié)) |
U+0041 |
A |
0x00000041 |
0x41 0x00 0x00 0x00 |
0x00 0x00 0x00 0x41 |
U+7834 |
破 |
0x00007834 |
0x34 0x78 0x00 0x00 |
0x00 0x00 0x78 0x34 |
U+6653 |
曉 |
0x00006653 |
0x53 0x66 0x00 0x00 |
0x00 0x00 0x66 0x53 |
U+2A6A5 |
?? |
0x0002A6A5 |
0xA5 0xA6 0x02 0x00 |
0x00 0x02 0xA6 0xA5 |
優(yōu)點是編碼定長容易進行文本處理,缺點是浪費存儲空間及存在字節(jié)序的問題。
C++11對Unicode的支持C++11對Unicode提供了語言級別和庫級別的支持。
USL(Unicode String Literals)USL是C++11對Unicode提供的語言級別的支持。在C++11之前C++中有個wchar_t的類型用于存儲寬字符(Wide-Character)。你可以寫下面這樣的代碼
1 2 3
| wchar_t wc = L'中'; wchar_t wc_array[] = L"破曉的博客"; std::wstring wstr = L"破曉的博客";
|
以L開頭的字符(或字符串)字面量稱為WCSL(Wide-Character String Literals)。本意大概也是用來提供Unicode支持的,可惜標準沒有規(guī)定這個的實現(xiàn),wchar_t及其字面量是實現(xiàn)相關的。比如
- 在windows平臺下sizeof(wchar_t)為2,而在linux平臺下sizeof(wchar_t)為4;
- 在windows平臺下寬字符(或字符串)字面量使用UTF-16編碼,linux平臺下使用UTF-32編碼。
這導致了下面這段代碼在windows下編譯時會報錯,而在linux下可以編譯通過。
1
| wchar_t wc = L'??'; // U+2A6A5
|
C++11新增了char16_t(至少16位)和char32_t(至少32位)以及USL允許下面這樣的代碼
1 2 3 4 5 6 7 8 9 10 11
| // utf-8 char u8_array[] = u8"破曉的博客"; std::string u8_str = u8"破曉的博客"; // utf-16 char16_t u16_c = u'中'; char16_t u16_array[] = u"破曉的博客"; std::u16string u16_str = u"破曉的博客"; // ucs4 char32_t u32_c = U'??'; char32_t u32_array[] = U"破曉的博客"; std::u32string u32_str = U"破曉的博客";
|
上面在字符(或字符串)字面量前面的u8、u及U前綴分別表示這是UTF-8、UTF-16和UCS4編碼的字符(或字符串)字面量,用法與L前綴類似。下面是一段測試代碼,print_code_uint_sequence函數(shù)模板用于輸出字符串的碼元序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <string> #include <iostream> #include <iomanip> #include <type_traits>
template<typename tStringType, typename tTraits = typename tStringType::traits_type> void print_code_unit_sequence(tStringType str) { using char_type = typename tTraits::char_type; static_assert(std::is_same<char_type, char>::value || std::is_same<char_type, char16_t>::value || std::is_same<char_type, char32_t>::value, "error"); using unsigned_char_type = typename std::make_unsigned<char_type>::type; using unsigned_int_type = typename std::make_unsigned<typename tTraits::int_type>::type; int w = std::is_same<char, char_type>::value ? 2 : std::is_same<char16_t, char_type>::value ? 4 : 8; for(auto c : str) { auto value = static_cast<unsigned_int_type>(static_cast<unsigned_char_type>(c)); std::cout << "0x" << std::hex << std::uppercase << std::setw(w) << std::setfill('0') << value << ' '; } }
int main() { std::string u8_str = u8"??"; // utf-8 std::u16string u16_str = u"??"; // utf-16 std::u32string u32_str = U"??"; // ucs4 std::cout << "utf-8: "; print_code_unit_sequence(u8_str); std::cout << std::endl; std::cout << "utf-16: "; print_code_unit_sequence(u16_str); std::cout << std::endl; std::cout << "ucs4: "; print_code_unit_sequence(u32_str); std::cout << std::endl; }
|
輸出
1 2 3
| utf-8: 0xF0 0xAA 0x9A 0xA5 utf-16: 0xD869 0xDEA5 ucs4: 0x0002A6A5
|
使用C++11標準庫進行編碼轉換C++11標準庫在<codecvt> 頭文件中定義了3個Unicode編碼轉換的Facet
Facet |
說明 |
std::codecvt_utf8 |
封裝了UTF-8與UCS2及UTF-8與UCS4的編碼轉換 |
std::codecvt_utf16 |
封裝了UTF-16與UCS2及UTF-16與UCS4的編碼轉換 |
std::codecvt_utf8_utf16 |
封裝了UTF-8與UTF-16的編碼轉換 |
當要轉換的字符串為std::basic_string使用<locale> 頭文件中定義的std::wstring_convert類模板會帶來極大的方便。
1 2 3 4 5
| template<class Codecvt, class Elem = wchar_t, class Wide_alloc = std::allocator<Elem>, class Byte_alloc = std::allocator<char>> class wstring_convert;
|
UTF-8與UTF-16編碼轉換UTF-8與UTF-16的編碼轉換使用std::codecvt_utf8_utf16類模板
1 2 3 4
| template<class Elem, unsigned long Maxcode = 0x10ffff, std::codecvt_mode Mode = (std::codecvt_mode)0> class codecvt_utf8_utf16 : public std::codecvt<Elem, char, std::mbstate_t>;
|
其中Elem用于存儲UTF-16碼元,可以是char16_t、char32_t或wchar_t(這些類型都至少能夠存儲16bit)。下面的代碼演示了UTF-8到UTF-16和UTF-16到UTF-8的編碼轉換
1 2 3 4 5 6 7 8 9 10 11 12 13
| std::string u8_source_str = u8"破曉的博客"; // utf-8 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> cvt; std::u16string u16_cvt_str = cvt.from_bytes(u8_source_str); // utf-8 to utf-16 std::string u8_cvt_str = cvt.to_bytes(u16_cvt_str); // utf-16 to utf-8 std::cout << "u8_source_str = "; print_code_unit_sequence(u8_source_str); std::cout << std::endl; std::cout << "u16_cvt_str = "; print_code_unit_sequence(u16_cvt_str); std::cout << std::endl; std::cout << "u8_cvt_str = "; print_code_unit_sequence(u8_cvt_str); std::cout << std::endl;
|
輸出
1 2 3
| u8_source_str = 0xE7 0xA0 0xB4 0xE6 0x99 0x93 0xE7 0x9A 0x84 0xE5 0x8D 0x9A 0xE5 0xAE 0xA2 u16_cvt_str = 0x7834 0x6653 0x7684 0x535A 0x5BA2 u8_cvt_str = 0xE7 0xA0 0xB4 0xE6 0x99 0x93 0xE7 0x9A 0x84 0xE5 0x8D 0x9A 0xE5 0xAE 0xA2
|
UTF-8與UCS2編碼轉換及UTF-8與UCS4編碼轉換UTF-8與UCS2或UCS4編碼轉換使用std::codecvt_utf8類模板
1 2 3 4
| template<class Elem, unsigned long Maxcode = 0x10ffff, std::codecvt_mode Mode = (std::codecvt_mode)0> class codecvt_utf8 : public std::codecvt<Elem, char, std::mbstate_t>;
|
當Elem為char16_t時轉換為UTF-8與UCS2,當Elem為char32_t時轉換為UTF-16與UCS4,當Elem為wchar_t時轉換取決于實現(xiàn)。演示如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| std::string u8_source_str = u8"破曉的博客"; // utf-8 std::wstring_convert<std::codecvt_utf8<char16_t>, char16_t> utf8_ucs2_cvt; std::u16string ucs2_cvt_str = utf8_ucs2_cvt.from_bytes(u8_source_str); // utf-8 to ucs2 std::string u8_str_from_ucs2 = utf8_ucs2_cvt.to_bytes(ucs2_cvt_str); // ucs2 to utf-8 std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf8_ucs4_cvt; std::u32string ucs4_cvt_str = utf8_ucs4_cvt.from_bytes(u8_source_str); // utf-8 to ucs4 std::string u8_str_from_ucs4 = utf8_ucs4_cvt.to_bytes(ucs4_cvt_str); // ucs4 to utf-8 std::cout << "u8_source_str: "; print_code_unit_sequence(u8_source_str); std::cout << std::endl; std::cout << "ucs2_cvt_str: "; print_code_unit_sequence(ucs2_cvt_str); std::cout << std::endl; std::cout << "u8_str_from_ucs2: "; print_code_unit_sequence(u8_str_from_ucs2); std::cout << std::endl; std::cout << "ucs4_cvt_str: "; print_code_unit_sequence(ucs4_cvt_str); std::cout << std::endl; std::cout << "u8_str_from_ucs4: "; print_code_unit_sequence(u8_str_from_ucs4); std::cout << std::endl;
|
輸出
1 2 3 4 5
| u8_source_str: 0xE7 0xA0 0xB4 0xE6 0x99 0x93 0xE7 0x9A 0x84 0xE5 0x8D 0x9A 0xE5 0xAE 0xA2 ucs2_cvt_str: 0x7834 0x6653 0x7684 0x535A 0x5BA2 u8_str_from_ucs2: 0xE7 0xA0 0xB4 0xE6 0x99 0x93 0xE7 0x9A 0x84 0xE5 0x8D 0x9A 0xE5 0xAE 0xA2 ucs4_cvt_str: 0x00007834 0x00006653 0x00007684 0x0000535A 0x00005BA2 u8_str_from_ucs4: 0xE7 0xA0 0xB4 0xE6 0x99 0x93 0xE7 0x9A 0x84 0xE5 0x8D 0x9A 0xE5 0xAE 0xA2
|
與UCS2進行轉換時需要注意的是,由于UCS2不能表示全部Unicode,所以當向UCS2轉換時UCS2無法表示時會拋出std::range_error異常。
UTF-16與UCS2編碼轉換及UTF-16與UCS4編碼轉換UTF-16轉UCS2或UCS4使用std::codecvt_utf16類模板
1 2 3 4
| template<class Elem, unsigned long Maxcode = 0x10ffff, std::codecvt_mode Mode = (std::codecvt_mode)0> class codecvt_utf16 : public std::codecvt<Elem, char, std::mbstate_t>;
|
這里以UTF-16與UCS4的轉換為例Elem為char32_t,UTF-16與UCS2的轉換類似只是Elem需為char16_t。 由于std::wstring_convert是用于在byte string和wide string之間轉換,使用std::codecvt_utf16時UTF-16字符串作為byte string,因此使用這個轉換時就需要考慮字節(jié)序的問題。std::codecvt_utf16類模板的第3個模板參數(shù)Mode類型為std::codecvt_mode
1 2 3 4 5
| enum codecvt_mode { consume_header = 4, generate_header = 2, little_endian = 1 };
|
這三個枚舉值的含義如下表
枚舉值 |
描述 |
consume_header |
告訴codecvt需要處理輸入的byte string中的BOM(Byte Order Mark) |
generate_header |
告訴codecvt在輸出的byte string中添加BOM |
little_endian |
告訴codecvt將byte string的字節(jié)序視為小端(Little Endian),默認為大端(Big Endian) |
下面的代碼演示了UTF-16 BE和UTF-16 LE編碼到UCS4編碼的轉換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| std::string u16le_byte_str = "\x34\x78\x53\x66"; // utf-16 Little Endian std::string u16be_byte_str = "\x78\x34\x66\x53"; // utf-16 Big Endian std::wstring_convert<std::codecvt_utf16<char32_t, 0x10ffff, std::little_endian>, char32_t> utf16le_cvt; // little endian std::wstring_convert<std::codecvt_utf16<char32_t>, char32_t> utf16be_cvt; // default big endian std::u32string u32_str_from_le = utf16le_cvt.from_bytes(u16le_byte_str); // utf-16 to ucs4 std::u32string u32_str_from_be = utf16be_cvt.from_bytes(u16be_byte_str); // utf-16 to ucs4 std::cout << "u16le_byte_str: "; print_code_unit_sequence(u16le_byte_str); std::cout << std::endl; std::cout << "u16be_byte_str: "; print_code_unit_sequence(u16be_byte_str); std::cout << std::endl; std::cout << "u32_str_from_le: "; print_code_unit_sequence(u32_str_from_le); std::cout << std::endl; std::cout << "u32_str_from_be: "; print_code_unit_sequence(u32_str_from_be); std::cout << std::endl;
|
輸出如下
1 2 3 4
| u16le_byte_str: 0x34 0x78 0x53 0x66 u16be_byte_str: 0x78 0x34 0x66 0x53 u32_str_from_le: 0x00007834 0x00006653 u32_str_from_be: 0x00007834 0x00006653
|
通過設置Mode成功將不同字節(jié)序UTF-16編碼的字符串進行了正確的轉換。
BOM(Byte Order Mark)字節(jié)序標記是插入到以UTF-8、UTF-16或UTF-32編碼Unicode文件開頭的特殊標記,用于標識文本編碼及字節(jié)序。
編碼 |
BOM |
UTF-8 |
0xEF 0xBB 0xBF |
UTF-16 BE |
0xFE 0xFF |
UTF-16 LE |
0xFF 0xFE |
UTF-32 BE |
0x00 0x00 0xFE 0xFF |
UTF-32 LE |
0xFF 0xFE 0x00 0x00 |
下面的代碼演示了通過BOM指示UTF-16編碼字符串的字節(jié)序,codecvt的第3個參數(shù)需設置為std::consume_header
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| std::string u16le_byte_str = "\xff\xfe\x34\x78\x53\x66"; // utf-16 little endian with BOM std::string u16be_byte_str = "\xfe\xff\x78\x34\x66\x53"; // utf-16 big endian with BOM std::wstring_convert<std::codecvt_utf16<char32_t, 0x10ffff, std::consume_header>, char32_t> utf16le_cvt; // little endian std::wstring_convert<std::codecvt_utf16<char32_t, 0x10ffff, std::consume_header>, char32_t> utf16be_cvt; // default big endian std::u32string u32_str_from_le = utf16le_cvt.from_bytes(u16le_byte_str); // utf-16 to ucs4 std::u32string u32_str_from_be = utf16be_cvt.from_bytes(u16be_byte_str); // utf-16 to ucs4 std::cout << "u16le_byte_str: "; print_code_unit_sequence(u16le_byte_str); std::cout << std::endl; std::cout << "u16be_byte_str: "; print_code_unit_sequence(u16be_byte_str); std::cout << std::endl; std::cout << "u32_str_from_le: "; print_code_unit_sequence(u32_str_from_le); std::cout << std::endl; std::cout << "u32_str_from_be: "; print_code_unit_sequence(u32_str_from_be); std::cout << std::endl;
|
輸出
1 2 3 4
| u16le_byte_str: 0xFF 0xFE 0x34 0x78 0x53 0x66 u16be_byte_str: 0xFE 0xFF 0x78 0x34 0x66 0x53 u32_str_from_le: 0x00003478 0x00005366 u32_str_from_be: 0x00007834 0x00006653
|
當UCS4轉UTF-16時輸出為byte string,可以通過設置Mode為std::generate_header來使輸出帶BOM,下面的代碼通過UCS4轉UTF-16 LE和UTF-16 BE演示了std::generate_header的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| std::u32string u32_str = U"破曉"; // ucs4 std::wstring_convert<std::codecvt_utf16<char32_t, 0x10ffff, static_cast<std::codecvt_mode>(std::generate_header | std::little_endian)>, char32_t> utf16le_cvt; // little endian std::wstring_convert<std::codecvt_utf16<char32_t, 0x10ffff, static_cast<std::codecvt_mode>(std::generate_header)>, char32_t> utf16be_cvt; // default big endian std::string u16le_byte_str = utf16le_cvt.to_bytes(u32_str); // ucs4 to utf-16 little endian with BOM std::string u16be_byte_str = utf16be_cvt.to_bytes(u32_str); // ucs4 to utf-16 big endian with BOM std::cout << "u32_str: "; print_code_unit_sequence(u32_str); std::cout << std::endl; std::cout << "u16le_byte_str: "; print_code_unit_sequence(u16le_byte_str); std::cout << std::endl; std::cout << "u16be_byte_str: "; print_code_unit_sequence(u16be_byte_str); std::cout << std::endl;
|
輸出
1 2 3
| u32_str: 0x00007834 0x00006653 u16le_byte_str: 0xFF 0xFE 0x34 0x78 0x53 0x66 u16be_byte_str: 0xFE 0xFF 0x78 0x34 0x66 0x53
|
|