UTF-8,Unicode,GB2312编码转换(C语言)
最近在做MQTT客户端的时候,遇到需要将输入的GB2312中文转换为UTF8的问题,这边做一个整理。
UTF-8转GB2312
代码:
/*!
* \brief UTF8至Gb2312 转换函数
*
* \details UTF8 -> Unicode -> Gb2312
*
* \param const char* gb 输入UTF8字符串
* int len 输入长度
* char *temp 输出Gb2312字符串
*
*/
void Utf8ToGb2312(const char* utf8, int len, char *temp)
{
int byteCount = 0;
int i = 0;
int j = 0;
while (i < len)
{
switch(GetUtf8ByteNumForWord((char)utf8[i]))
{
case 0:
temp[j] = utf8[i];
byteCount = 1;
break;
case 2:
temp[j] = utf8[i];
temp[j + 1] = utf8[i + 1];
byteCount = 2;
break;
case 3:
//这里就开始进行UTF8->Unicode
temp[j] = ((utf8[i] & 0x0F) << 4) | ((utf8[i + 1] >> 2) & 0x0F);
temp[j+1] = ((utf8[i + 1] & 0x03) << 6) + (utf8[i + 2] & 0x3F);
//取得Unicode的值
memcpy(&unicodeKey, (temp + j), 2);
//根据这个值查表取得对应的GB2312的值
gbKey = getgb(unicodeKey);
/* 因为gbKey是unsigned short类型,STM32是小端存储,保存成char类型时需要转一下 */
if (gbKey != 0)
{
gbKey = (gbKey >> 8) | (gbKey << 8);
memcpy((temp + j), &gbKey, 2);
}
byteCount = 3;
break;
case 4:
byteCount = 4;
break;
case 5:
byteCount = 5;
break;
case 6:
byteCount = 6;
break;
default:
break;
}
i += byteCount;
if (byteCount == 1)
{
j++;
}
else
{
j += 2;
}
}
}
/*!
* \brief 计算当前字符包含字节数
*
* \details 通过第一个字节的高位1的数量计算
*
* \param char firstCh 第一个字节
*
* \retval 字节数
*/
int GetUtf8ByteNumForWord( char firstCh )
{
char temp = 0x80;
int num = 0;
while (temp & firstCh)
{
num++;
temp = (temp >> 1);
}
return num;
}
const unsigned short giGBCount=21327;
/*!
* \brief Unicode -> Gb2312
*
* \details 通过查表将Unicode转为Gb2312
*
* \param unicode 输入的Unicode
*
* \retval 转换得到的Gb2312
*/
static unsigned short getgb(unsigned short int unicode)
{
int i;
for(i=0; i<giGBCount; i++)
{
if (giGB2312[i][1] == ntohs(unicode))
{
return ntohs(giGB2312[i][0]);
}
}
return 0xFFFF;
}
引来一段UTF-8与Unicode的转换规则:
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
第一个字节高位有几个1就代表此字符由几个字节组成,可以以此解析一长串UTF-8,如GetUtf8ByteNumForWord中的实现。比如当拿到一串字节流时,第一个字节是11100110,高位有3个1,那么再往后取2个字节,总共3个字节,假设得到:11100110 10110001 10001001 根据3字节结构即:1110-0110 10-110001 10-001001,提取出来即0110 1100 0100 1001,最后得到Unicode码:6C49,是“汉”字。函数getgb是一个简单的查表函数,giGB2312是转换码表,手头没有的朋友可以在GB2312,Unicode码表下载 。函数ntohs以及后面的htons都是大小端转换函数,如果机器是小端则需要转换,如果是大端则不需要。
GB2312转UTF-8
代码:
/*!
* \brief Gb2312至UTF8 转换函数
*
* \details Gb2312 -> Unicode -> UTF8
*
* \param const char* gb 输入Gb2312字符串
* int len 输入长度
* char *temp 输出UTF8字符串
*
* \retval 转换得到的UTF8长度
*/
int Gb2312ToUtf8(const char* gb, int len, char *temp)
{
int i=0,j=0;
unsigned short iTmp;
while(i<len)
{
if ((unsigned char)gb[i] <= 0xa0)
{
iTmp=gb[i];
iTmp=htons(iTmp);
i++;
}
else
{
iTmp=getun(*(unsigned short int*)(gb+i));
i+=2;
}
/* 从getun返回的时候已经被转成大端了,下面再转成小端*/
iTmp = iTmp >> 8 | iTmp << 8;//这里本没有转的必要,getun由于系统是小端将数据转了一下,这里转回来
if(iTmp <= 0x7F)
{
temp[j] = iTmp;
j++;
}
else if(iTmp <= 0x7FF)
{
temp[j] = (iTmp >> 6) | 0xC0;
temp[j+1] = (iTmp & 0x3F) | 0x80;
j += 2;
}
else if(iTmp <= 0xFFFF)
{
temp[j] = (iTmp >> 12) | 0xE0;
temp[j+1] = ((iTmp >> 6) & 0x3F) | 0x80;
temp[j+2] = (iTmp & 0x3F) | 0x80;
j += 3;
}
else
{
/*暂不考虑支持*/
}
}
return j;
}
/*!
* \brief Gb2312 -> Unicode
*
* \details 通过查表将Gb2312转为Unicode
*
* \param unicode 输入的Gb2312
*
* \retval 转换得到的Unicode
*/
static unsigned short getun(unsigned short int gb)
{
int i;
for(i=0;i<giGBCount;i++)
{
if (giGB2312[i][0]==gb)
{
return htons(giGB2312[i][1]);
}
}
return 0xFFFF;
}
结语
代码中涉及到一些大小端的概念,如果想从事网络编程相关的工作必须了解。
参考资料:
UTF-8, Unicode, GB2312格式串转换之C语言版
你的机器是大端还是小端?