C++11 Unicode编码转换
1.char16_t与char32_t
在C++98中,为了支持Unicode字符,使用wchar_t类型来表示“宽字符”,但并没有严格规定位宽,而是让wchar_t的宽度由编译器实现,因此不同的编译器有着不同的实现方式,GNUC++规定wchar_t为32位,VisualC++规定为16位。由于wchar_t宽度没有一个统规定,导致使用wchar_t的代码在不同平台间移植时,可能出现问题。这一状况在C++11中得到了一定的改善,从此Unicode字符的存储有了统一类型:
(1)char16_t:用于存储UTF-16编码的Unicode字符。
(2)char32_t:用于存储UTF-32编码的Unicode字符。
至于UTF-8编码的Unicode数据,C++11还是使用了8bits宽度的char类型数组来表示,而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中有两种定义字符串的方式,一是直接使用双引号定义多字节字符串,二是通过前缀“L”表示wchar_t字符串(宽字符串)。至此,C++中共有5种定义字符串的方式。
3.影响字符串正确处理的因素
在使用不同方式定义不同编码的字符串时,我们需要注意影响字符串处理和显示的几个因素有编辑器、编译器和输出环境。
代码编辑器采用何种编码方式决定了字符串最初的编码,比如编辑器如果采用GBK,那么代码文件中的所有字符都是以GBK编码存储。当编译器处理字符串时,可以通过前缀来判断字符串的编码类型,如果目标编码与原编码不同,则编译器会进行转换,比如C++11中的前缀u8表示目标编码为UTF-8的字符,如果代码文件采用的是GBK,编译器按照UTF-8去解析字符串常量,则可能会出现错误。
//代码文件为GBK编码 #include#include usingnamespacestd; intmain() { constchar*sTest=u8"你好"; for(inti=0;sTest[i]!=0;++i) { cout< 程序输出结果:C4E3BAC3。这个码值是GBK的码值,因为“你”的GBK码值是0xC4E3,“好”的GBK码值是0xBAC3。可见,编译器未成功地将GBK编码的“你好”转换为UTF-8的码值“你”(E4BDA0)“好”(E5A5BD),原因是使用编译选项-finput-charset=utf-8指定代码文件编码为UTF-8,而实际上代码文件编码为GBK,导致编译器出现错误的认知。如果使用-finput-charset=gbk,那么编译器在编译时会将GBK编码的“你好”转换为UTF-8编码,正确输出E4BDA0E5A5BD。
代码编辑器和编译器这两个环节在处理字符串如果没有问题,那么最后就是显示环节。字符串的正确显示依赖于输出环境。C++输出流对象cout能够保证的是将数据以二进制输出到输出设备,但输出设备(比如Linuxshell或者Windowsconsole)是否能够支持特定的编码类型的输出,则取决于输出环境。比如Linux虚拟终端XShell,配置终端编码类型为GBK,则无法显示输出的UTF-8编码字符串。
一个字符串从定义到处理再到输出,涉及到编辑器、编译器和输出环境三个因素,正确的处理和显示需要三个因素的共同保障,每一个环节都不能出错。一个字符串的处理流程与因素如下图所示:
当然如果想避开编辑器编码对字符串的影响,可以使用Unicode码值来定义字符串常量,参看如下代码:
//代码文件为GBK编码 #include#include usingnamespacestd; intmain() { constchar*sTest=u8"\u4F60\u597D";//你好的Uunicode码值分别是:0x4F60和0x597D for(inti=0;sTest[i]!=0;++i) { cout< 程序输出结果:E4BDA0E5A5BD。可见,即使编译器对代码文件的编码理解有误,仍然可以正确地以UTF-8编码输出“你好”的码值。原因是ASCII字符使用GBK与UTF-8编码码值是相同的,所以直接书写Unicode码值来表示字符串是一种比较保险的做法,缺点就是难以阅读。
4.Unicode的库支持
C++11在标准库中增加了一些Unicode编码转换的函数,开发人员可以使用库中的一些新增编码转换函数来完成各种Unicode编码间的转换,函数原型如下:
//多字节字符转换为UTF-16编码 size_tmbrtoc16(char16_t*pc16,constchar*pmb,size_tmax,mbstate_t*ps); //UTF-16字符转换为多字节字符 size_tc16rtomb(char*pmb,char16_tc16,mbstate_t*ps); //多字节字符转换为UTF-32编码 size_tmbrtoc32(char32_t*pc32,constchar*pmb,size_tmax,mbstate_t*ps); //UTF-32字符转换为多字节字符 size_tc32rtomb(char*pmb,char32_tc32,mbstate_t*ps);函数名称中mb表示multi-byte(多字节),rto表示convertto(转换为),c16表示char16_t,了解这些,可以根据函数名称直观的理解它们的作用。下面给一下UTF-16字符串转换为多字节字符串(以GBK为例)的例子:
#include#include #include #include #include usingnamespacestd; intmain() { constchar16_t*utf16=u"\u4F60\u597D\u554A"; size_tutf16Len=char_traits ::length(utf16); char*gbk=newchar[utf16Len*2+1]; memset(gbk,0,utf16Len*2+1); char*pGbk=gbk; setlocale(LC_ALL,"zh_CN.gbk"); mbstate_tmbs;//转换状态 size_tlength=0; while(*utf16) { pGbk+=length; length=c16rtomb(pGbk,*utf16,&mbs); if(length==0||pGbk-gbk>sizeof(gbk)) { cout<<"failed"< 程序输出结果:C4E3BAC3B0A1。可见,使用c16rtomb()完成了将“你好啊”从UTF-16编码到多字节编码(GBK)的转换。上面的转换,我们用到了locale机制。locale表示的是一个地域的特征,包括字符编码、数字时间表示形式、货币符号等。locale串使用“zh_CN.gbk”表示目的多字节字符串使用GBK编码。
上面通过Unicode字符的转换来完成字符串的转换,实际上C++提供了一个类模板codecvt用于完成Unicode字符串与多字节字符串之间的转换,主要分为4种:
codecvt//performsnoconversion codecvt //convertsbetweennativewideandnarrowcharactersets codecvt //convertsbetweenUTF16andUTF8encodings,sinceC++11 codecvt //convertsbetweenUTF32andUTF8encodings,sinceC++11 上面的codecvt实际上是locale的一个facet,facet可以简单地理解为locale的一些接口。通过codecvt,可以完成当前locale下多字节编码字符串与Unicode字符间的转换,也包括Unicode字符编码间的转换。这里的多字节字符串不仅可以试UTF-8,也可以是GBK或者其它编码,实际依赖于locale所采用的编码方式。每种codecvt负责不同类型编码的转换,但是目前编译器的支持情况并没有那么完整,一种locale并不一定支持所有的codecvt,程序员可以通过has_facet函数模板来查询指定locale下的支持情况。参考代码如下:
#include#include usingnamespacestd; intmain() { //定义一个locale并查询该locale是否支持一些facet localelc("zh_CN.gbk"); boolcan_cvt=has_facet >(lc); if(!can_cvt) cout<<"donotsupportchar-charfacet"< >(lc); if(!can_cvt) cout<<"donotsupportwchar_t-charfacet"< >(lc); if(!can_cvt) cout<<"donotsupportchar16_t-charfacet"< >(lc); if(!can_cvt) cout<<"donotsupportchar32_t-charfacet"< 程序输出结果:
donotsupportchar16_t-charfacet
donotsupportchar32_t-charfacet由此可见,从char到char16_t与char32_t转换的两种facet还没有被实验机使用的编译器支持。
假如实验机支持从char与char16_t的转换,可参考如下代码:
#include#include #include #include #include usingnamespacestd; intmain() { typedefstd::codecvt facet_type; std::localemylocale("zh_CN.gbk"); try { constfacet_type&myfacet=std::use_facet (mylocale); constchar16_t*utf16=u"\u4F60\u597D\u554A";//你好啊 size_tutf16Len=char_traits ::length(utf16); cout< 由于实验环境并不支持char与char16_t相互转换的facet,所以程序输出结果为:donotsupportchar16_t-charfacet。
5.u16string与u32string
C++11新增了UTF-16和UTF-32编码的字符类型char16_t和char32_t,当然少不了对应的字符串类型,分别是u16string与与u32string,二者的存在类似与string与wstring。四者的定义如下:
typedefbasic_stringstring; typedefbasic_string wstring; typedefbasic_string u16string; typedefbasic_string u32string; 我们对string与wstring应该比较熟悉,对于u16string与u32string在用法上是差不多了,有相同的成员接口与类型,只需要记住其存储的字符编码类型不同即可。下面看一下u16string使用的简单示例。
#include#include usingnamespacestd; intmain() { u16stringu16str=u"\u4F60\u597D\u554A";//你好啊 cout< 程序输出:
3
4F60597D554A。以上就是C++11Unicode编码转换的详细内容,更多关于C++11Unicode编码转换的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。