c++ string/char* 字符编码转换 (utf8,chs,gbk...)

本文用于介绍不同编码格式的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-4bit数UTF-8byte数备注
0000 ~ 007F0~70XXX XXXX1ASIC 码
0080 ~ 07FF8~11110XXXXX 10XXXXXX2
0800 ~ FFFF12~161110 XXXX 10XXXXXX 10XXXXXX3以上基本定义范围:0~FFFF
1 0000 ~ 1F FFFF17~2111110XXX 10XXXXXX 10XXXXXX 10XXXXXX4Unicode6.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开源库。

Sharding-JDBC 是一个开源的分库分表中间件,它可以帮助我们实现数据库的水平扩展和分布式架构。在 Sharding-JDBC 中,强制分片路由策略可以用于实现分表操作。 强制分片路由策略是一种在分表操作时,强制路由到指定的分片的策略。通常,在分表操作中,我们会通过某种规则或算法来决定数据应该路由到哪个分片上,比如根据某个字段的哈希值进行路由。而强制分片路由策略则会忽略这些规则,直接将数据路由到指定的分片。 在 Sharding-JDBC 中,我们可以通过配置数据源、分片规则和强制路由规则来实现强制分片路由策略。配置文件中的示例代码如下: ```xml <sharding-rule> <binding-tables> <binding-tables-strategy> <standard> <sharding-column>user_id</sharding-column> <algorithm-expression>user_id % 2</algorithm-expression> </standard> </binding-tables-strategy> </binding-tables> <table-rule> <table>user</table> <actual-data-nodes>ds${0..1}.user_${0..1}</actual-data-nodes> <database-strategy> <standard> <sharding-column>user_id</sharding-column> <algorithm-expression>user_id % 2</algorithm-expression> </standard> </database-strategy> <table-strategy> <standard> <sharding-column>user_id</sharding-column> <algorithm-expression>user_id % 2</algorithm-expression> </standard> </table-strategy> </table-rule> </sharding-rule> ``` 在上述配置中,`user_id` 是用来分片的字段,我们通过对 `user_id` 进行取模运算来实现分表路由。同时,我们可以通过配置强制路由规则,将数据强制路由到指定的分片。 需要注意的是,强制分片路由策略在某些特定场景下可能会有一定的限制和风险,因此在使用时需谨慎评估。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aworkholic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值