【CPP开发者导读】:在处理东方语言(中日韩)时,经常会遇到各种编码问题,而且被这类问题搞的晕头转向。到网上查资料,看的也是一头雾水,最后往往是误打误撞的把问题解决了,自己仍然稀里糊涂。
/ h( O V+ b& _( z5 q- `
/ E* w0 N6 D; ~0 e. Q# M# X
这篇文章介绍了如何在最常见的编码方式(Unicode, UTF-8, ANSI)之间进行转换,结合代码实例,清晰明了,方便读者理解,例子也可以直接拿来使用。本文推荐给经常对文字字符串进行处理的程序员阅读,使其掌握字符转换的一些基本方法。
4 @7 b& f! n2 Y% j1 L2 D
; b" R- d) D7 y3 G4 H4 m" A
以下是正文
6 h9 q5 }0 W4 t0 B, t. E
C++的项目,字符编码是一个大坑,不同平台之间的编码往往不一样,如果不同编码格式用一套字符读取格式读取就会出现乱码。因此,一般都是转化成UTF-8这种平台通用,且支持性很好的编码格式。, w* ~0 ?( W6 l6 t6 _9 ?
Unicode、UTF-8的概念不做过多解释,这里说一下ANSI,我第一次看到这个名词,我看成了ASCII。被Mentor狠批一顿。
, y5 h- D1 Q1 a& Q- j1 X4 r 7 g/ s: j& {9 p6 z3 F# o# f+ a
ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00 ~ 0x7F范围的1 个字节来表示 1 个英文字符。超出此范围使用0x80~0xFFFF来编码,即扩展的ASCII编码。
6 L5 C; L6 x* E4 R
不同的国家和地区制定了不同标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统,ANSI编码代表Big5;在日文Windows操作系统,ANSI 编码代表 Shift_JIS 编码。
以上内容摘自百度百科,可以看出,ANSI和ASCII还是有关系的。ANSI也叫本地码。
T# r- Z. y% v8 }) ^( r 我们要做到能在Unicode、UTF-8、ANSI这三种编码格式中自由转换。如下图所示:
# x; V% {3 Y, T4 e# d. q/ ]
; T( s1 t$ |8 u% w7 ]* k
在C++中,要怎么做呢?当然是用标准库的东西啦,C++11对国际化标准做得还是可以的,提供了这些接口,正如图中虚线所示,标准库没有提供UTF-8到ANSI的互相转化接口,但是我们可以自己封转接口,借用这条路(UTF-8 <=> Unicode <=> ANSI)来实现。
E# |' K4 s. U$ q3 w O5 b+ B
因此,接下来就聊聊UTF8 <=> Unicode和Unicode <=> ANSI。 / [( a5 q' J) m8 M
UTF8 <=> Unicode
, ~( Y/ j) Z( v2 t3 @" v! }
先看代码: - & | D% G- z! e: B2 v
- std::string UnicodeToUTF8(const std::wstring & wstr)9 q* `: S) |" z; C7 b. E, O
- {
3 F5 Y7 c! D7 X' n5 x9 k, m. ~ - std::string ret;$ s/ Y+ J4 d! z+ i7 u
- try {
+ R' K* H1 k5 Q- J! @ - std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;3 Y8 \2 o! }2 a6 `; B
- ret = wcv.to_bytes(wstr);' c( L, L+ n2 Z1 F( ] j
- } catch (const std::exception & e) {
5 ?' S* a& S) s' [& E" \ - std::cerr << e.what() << std::endl;
C, f) U) L; h& n3 B - }' l, ?4 `# M/ B: V6 d/ O! ^2 Q \
- return ret;
; w' l5 p% q+ H* O: v - }2 I. i( Z! {. v) A' X m" q
- std::wstring UTF8ToUnicode(const std::string & str)
, f' L9 e" ~" [, c. X5 ~6 _! S - {3 ]" l5 t# E1 v! n/ Z% G7 b
- std::wstring ret;
' Y9 d5 j2 L" y' U - try {1 C9 x m: o. M/ i8 ^3 G
- std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;
; s/ y9 q- b! y) A7 R( F# y' ? - ret = wcv.from_bytes(str);9 z& K* y5 O H) f& M9 C' y
- } catch (const std::exception & e) {2 s4 Z9 G; Q) ]4 D8 n
- std::cerr << e.what() << std::endl;
' @5 D9 J1 v0 _ - }
/ u5 O% ^3 A. a6 s [0 p! n - return ret;
N$ v6 q! K) p - }
复制代码) e% @) b9 i7 J W9 N
UTF-8是多字节字符串(multibyte string),而Unicode是宽字符字符串(wchar_t string)。 + w2 f; V; s. h a
而C++11提供了wstring_convert这个类,这个类可以在wchar_t string和multibyte string之间来回转换;
+ O& p- H% E6 I) m0 o
而codecvt_utf8可以提供UTF-8的编码规则。这个类在#include <codecvt>中。有了wstring_convert提供宽字符字符串到多字节字符串的转化,而这个转换规则由codecvt_uft8提供。这样子就可以实现UTF8和Unicode的互相转换。 7 ?9 ?$ Y: m. C
' G; R% Z2 d. F7 O, X( z6 H0 `/ l
Unicode <=> ANSI
" g F, k) b5 F. k2 a3 ~; P' p- std::string UnicodeToANSI(const std::wstring & wstr), |5 `% K: A+ ^: H! A' D) C
- {
; Q/ ]& i5 m2 \% y; A - std::string ret;; u- y; G" F' M( w0 U; j5 x
- std::mbstate_t state = {};, ^3 t; E( l" A% }9 N9 B5 z
- const wchar_t *src = wstr.data();
* T9 J. ~1 x) M% }9 f - size_t len = std::wcsrtombs(nullptr, &src, 0, &state);. l4 w: M% g- y
- if (static_cast<size_t>(-1) != len) {
& w7 O0 W2 l7 o, E. I - std::unique_ptr< char [] > buff(new char[len + 1]);- `3 E4 }5 |; l( P
- len = std::wcsrtombs(buff.get(), &src, len, &state);
/ p) D* }! L% v: G - if (static_cast<size_t>(-1) != len) {0 w4 P8 A6 w& X
- ret.assign(buff.get(), len);7 ?# O" ~1 j; Q. f) i" F0 M2 T
- }/ r, ~) u D& ?+ ?+ t% F; y
- }3 F6 E/ G( N1 C a- L
- return ret;2 \' v4 I$ o0 l: |0 S1 G& T
- }
* U3 r! Z @) U; J: Z" _& x. c/ c - std::wstring ANSIToUnicode(const std::string & str)
* c9 _" q% Y0 I4 ]! r9 m - {
; r* m1 G2 h% @" C# s - std::wstring ret;. f8 p& y0 g) s& X5 @: n P$ a
- std::mbstate_t state = {};
A# c+ w( ^1 L* d% J$ p; j4 Z1 J - const char *src = str.data();
3 e' [1 j" [1 ?7 d9 ^5 E J% G4 j - size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
6 k. p0 p1 y, [, E) G% d* { - if (static_cast<size_t>(-1) != len) {
; M6 A6 W/ ?9 g+ e0 K. o, K - std::unique_ptr< wchar_t [] > buff(new wchar_t[len + 1]);. a$ p% L8 j0 y( c2 q3 e3 ^6 G
- len = std::mbsrtowcs(buff.get(), &src, len, &state);& A$ P; e, d/ W8 g7 z2 z" ~
- if (static_cast<size_t>(-1) != len) {! h7 H7 f0 Q; \) R6 C) P1 I$ L
- ret.assign(buff.get(), len);6 p2 m/ Z' [" N+ L9 Q
- }, J: \8 o3 E( n, x. j5 w
- }5 [, |' Y$ b& T" r5 _9 f/ a
- return ret; X7 Q* b9 s1 y' K
- }
复制代码! X6 M- V. }/ `5 H$ o( L
标准库提供了wcsrtombs和mbsrtowcs这两个函数,当然C的标准库也提供了这两个函数。 2 @3 T* r3 L7 ~! T3 B1 @. U1 {+ Q
讲下wcsrtombs,这个函数把宽字符串转成多字节字符串。编码规则受地域的LC_CTYPE影响。因此这个函数可以用于本地码的转化(和本地的编码息息相关)。 : m7 X9 _" C V% K
因此,有关于本地码的使用,在代码中要加上下列语句: $ f Q+ \& y7 e6 W
setlocale(LC_CTYPE, "");
V* x3 Q" o4 n9 H- F1 s( E0 ]
目的是让本地码生效,这条代码的作用就是让C++语言的Locale(地域)和本地的地域相同。在Linux下可以运行locale命令看看: 可以看到,LC_CTYPE = en_US.UTF-8,这表示英文,英国,UTF-8编码,也就是说本地码就是这个。
# C" P s) _" \) c3 u! n; H' z Z" v5 u+ m' j% R L1 e- E
当然,你也可以在setlocale中指定一些编码规则,把wcsrtombs用于别的编码转化,但是,这里不推荐,因为setlocale是全局的,设置了这个就会影响其他地方的编码。 wcsrtombs的四个参数分别代表什么意思呢? % `7 ^ C# n6 N1 @" Q8 f7 u8 p( B
std::size_t wcsrtombs( char* dst, const wchar_t** src, std::size_t len, std::mbstate_t* ps ); 5 Z7 H0 p7 Q [9 M9 [& s
7 V' k* ~) P% j8 t$ Q% P- Y" ^
注意:如果dst == nullptr,这个时候wcstombs的返回值表示会有这么多字节的结果产生,因此,我们可以拿到这个返回值去新建一个数组来存储new char[len + 1]。所以,一般调用两次wcstombs。 / b8 f9 g0 j; I9 N6 a8 K
mbsrtowcs同理。
0 ^ ^2 {$ V, t7 _% w9 ^* f1 {, ^: i+ G
UTF-8 <=> ANSI
* ~+ e V' X x8 S; _
以Unicode为中介装换便是。 - 4 [. I# e6 b+ ~5 v4 U$ d0 M/ D2 V( ]
- std::string UTF8ToANSI(const std::string & str)7 v! n0 E) X7 M g' @9 C
- {0 r( a/ w, _( I3 d7 d
- return UnicodeToANSI(UTF8ToUnicode(str));
9 b( p; \& d3 _! Q' u - }
0 C5 g: v9 s+ B+ E4 N - std::string ANSIToUTF8(const std::string & str) v6 i- X/ l! P5 x! n
- {
2 {9 Q* S: O3 ]2 r9 n d( } - return UnicodeToUTF8(ANSIToUnicode(str));
# o# J! n1 B f. m* ?/ u - }
复制代码
5 s+ l* }- K/ N总结
3 x( ~( @: J1 k6 C; C. e* A! o+ z c
C++11的标准库还是挺强大的,虽然这么强大,但是很多特性还不了解,因此还是要多扩宽自己的视野,不然有好东西都不知道用,那就棒槌了。 , L6 d$ }" G2 _3 B6 s W
对了,在Linux下加上setlocale(LC_CTYPE, "")后程序在命令行中可以正常显示,不加有可能不正常显示,原因是setlocale(LC_CTYPE, "")也影响了cout,全局的嘛;而在CodeBlocks下不能正常显示,不知道为什么,但是调试的过程中,观察到了正常的结果;Visual Studio中没有做实验,不过应该没问题。
3 |, M0 {$ o, f$ y0 F) P) n7 u |