本文用于介绍不同编码格式的string(char*)之间的转换。明确Unicode,UTF-8,string,wstring概念,以及locale name之前, 先简单了解两个概念
- 字符集: 为每一个字符(asic,中文,日文,俄文等)分配一个唯一的ID(又称码位)。
- 编码规则:将码位转换为字节序列的规则(编码/解码的过程)
由于UTF-8使用广泛,以utf-8编码为例,介绍其与其它编码方式的流程。
UTF-8
广义的Unicode的一个标准,定义了一个字符集以及一系列的编码规则,即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码……
UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符。会将一个码位编码为 1 到 4 个字节(理论最多6个字节):
- 1字节 一个US-ASCIl字符(Unicode范围由U+0000~U+007F)。
- 2字节 带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母(Unicode范围由U+0080~U+07FF)。
- 3字节 其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。
- 4字节 其他极少使用的语言字符使用4字节编码。
Unicode/UCS-4 | bit数 | UTF-8 | byte数 | 备注 |
---|---|---|---|---|
0000 ~ 007F | 0~7 | 0XXX XXXX | 1 | ASIC 码 |
0080 ~ 07FF | 8~11 | 110XXXXX 10XXXXXX | 2 | |
0800 ~ FFFF | 12~16 | 1110 XXXX 10XXXXXX 10XXXXXX | 3 | 以上基本定义范围:0~FFFF |
1 0000 ~ 1F FFFF | 17~21 | 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX | 4 | Unicode6.1定义范围:0~10 FFFF |
上表是Unicode中任意字符使用utf-8编码的规则:如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。
例如「知」的码位是 30693,记作 U+77E5(30693 的十六进制为 0x77E5),其对应的UTF-8 编码为字节序列 E79FA5。
根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围,使用三个3个字节编码,将0x77E5的二进制位从地位开始放入编码模板的
7 7 E 5
0111 0111 1110 0101 二进制的 77E5
--------------------------
0111 011111 100101 二进制的 77E5
1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)
11100111 10011111 10100101 代入模版
E 7 9 F A 5
这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程。反之亦然。
判断字符串是否为utf-8编码
字符串可能存储在std::string或者char*中,前者实际是 char 内存的一个封装,后续说明全部以std::string为例。
了解utf-8编码规则,首先可以根据每个字节的最高比特位判断其为单字节多字节编码,若为0,则可能是单字节,还需继续判断。 若为1,则需要查看当前是几个字节,且后续字节的前两位必须是10。直到所有字节判断结束。
bool IsTextUTF8(const std::string& str)
{
char nBytes=0;//UFT8可用1-6个字节编码,ASCII用一个字节
unsigned char chr;
bool bAllAscii = true; //如果全部都是ASCII, 说明不是UTF-8
for(int i=0; i < str.length();i++)
{
chr = str[i];
// 判断是否ASCII编码,如果不是,说明有可能是UTF-8,ASCII用7位编码,
// 但用一个字节存,最高位标记为0,o0xxxxxxx
if( (chr&0x80) != 0 )
bAllAscii= false;
if(nBytes==0) //如果不是ASCII码,应该是多字节符,计算字节数
{
if(chr>=0x80)
{
if(chr>=0xFC&&chr<=0xFD) nBytes=6;
else if(chr>=0xF8) nBytes=5;
else if(chr>=0xF0) nBytes=4;
else if(chr>=0xE0) nBytes=3;
else if(chr>=0xC0) nBytes=2;
else{
return false;
}
nBytes--;
}
}
else //多字节符的非首字节,应为 10xxxxxx
{
if( (chr&0xC0) != 0x80 ){
return false;
}
nBytes--;
}
}
if( nBytes > 0 ) //违返规则
return false;
if( bAllAscii ) //如果全部都是ASCII, 说明不是UTF-8
return false;
return true;
}
不同编码的std::string转换
c/c++修改字符集locale name可能会影响系统正在运行的有关字符编解码的程序,所以使用C++11的std::wstring_convert配合std::codecvt模板类。
- std::codecvt:编码转换特性类,用在wstring_convert的模板参数中来指定使用哪种编码。
- std::wstring_convert:转码器,接收一个类似codecvt描述编码转换特性的模板参数,用于将本地化的宽字符wstring和指定编码的字节化string进行互转。
所以编码A和B互转的实现方式就是:借助本地化宽字符串,先将以A编码的string转为本地化的wstring,再将本地化的wstring转为B编码后的string。
示例一:
这里给一个windows下,GBK string转UTF8 string的例子:
首先将GBK string转wstring
const char* GBK_LOCALE_NAME = ".936"; //GBK在windows下的locale name
std::string gbk_str {"\xCC\xCC"}; //0xCCCC,"烫"的GBK码
//构造GBK与wstring间的转码器(wstring_convert在析构时会负责销毁codecvt,
// 所以不用自己delete)
std::wstring_convert<codecvt_byname<wchar_t, char, mbstate_t>>
// 或 std::wstring_convert<std::codecvt<wchar_t, char, mbstate_t>>
cv1(new std::codecvt<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME));
wstring tmp_wstr = cv1.from_bytes(gbk_str);
再将wstring转为UTF8 string
std::wstring_convert<codecvt_utf8<wchar_t>> cv2;
std::string utf8_str = cv.to_bytes(tmp_wstr);
转码就完成了。utf8_str里的内容应该是"\xE7\x83\xAB"(烫的UTF8)。
转换的完整代码
std::string StringToUTF8(const std::string& gbkData)
{
const char* GBK_LOCALE_NAME = "CHS"; //GBK在windows下的locale name(.936, CHS ), linux下的locale名可能是"zh_CN.GBK"
std::wstring_convert<std::codecvt<wchar_t, char, mbstate_t>>
conv(new std::codecvt<wchar_t, char, mbstate_t>(GBK_LOCALE_NAME));
std::wstring wString = conv.from_bytes(gbkData); // string => wstring
std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
std::string utf8str = convert.to_bytes(wString); // wstring => utf-8
return utf8str;
}
示例二:
例如在window下,将utf8编码转换为CHS编码的代码为
std::string UTF8ToString(const std::string& utf8Data)
{
std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
std::wstring wString = conv.from_bytes(utf8Data); // utf-8 => wstring
std::wstring_convert<std::codecvt< wchar_t, char, std::mbstate_t>>
convert(new std::codecvt< wchar_t, char, std::mbstate_t>("CHS"));
std::string str = convert.to_bytes(wString); // wstring => string
return str;
}
另外:
由于编码的locale name是操作系统决定的(例如GBK在linux下的locale名可能是"zh_CN.GBK",而windows下是".936"),因此做跨平台的话仍然要给不同的系统做适配。
项目中可以使用iconv开源库。