转自 http://blog.youkuaiyun.com/coollofty/article/details/8058859

本文介绍如何在没有系统API辅助的情况下,实现GBK、UTF8、Unicode三种常见编码间的转换。重点讲解了UTF8与Unicode间的直接转换算法,并通过查表法解决了GBK与Unicode之间的转换难题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GBK、UTF8、Unicode,这三种编码是一般程序开发,或者各种应用中最常见的三种编码方式了,还不知道基本概念的赶快请教搜索引擎自己科普一下。


本文的目的不是来讲述什么是GBK编码,什么是UTF8编码,他们的编码规则是怎样的这一类的基本概念的文章。本文要讲述的是如何没有系统API辅助的情况下,如何最简单的快速实现这三种编码之间的转换。也许你会说,这有什么意义呢?Windows上、Linux上,我们都有系统API可以进行转换,一个API搞定了。退一步说,还有libiconv可以垫底,何必自己搞?话是这么说没错,但这不等于所有的系统上都有这些编码转换的API,比如Android的NDK开发的时候,编码转换就是一个讨厌的问题,虽然Android自身有带这样的API,但是他没有提供给我们使用,网上的解决方案都存在着或多或少的问题,不是性能不成(通过Java转调,效率太低),就是自己去dl_open icu库,但是那个函数的名称又很蛋疼。而我们只是需要转换这么3种觉编码而已,搞出iconv来一下就是好几MB的库,真觉得没有必要。总之解决得都很不爽。咱是C++程序员,索性,本着底层开发的原则,自己搞一套吧。


这三种编码的转换,UTF8与Unicode之间是很简单的(这里的Unicode指的是UCS-2),直接贴代码:

 

 

 

 

 

01.//参数1是UTF8字符串当前位置指针,这里必须要是指针,因为必须要通过第1个字符进行判断才知道一个完整的字符的编码要向后取多少个字符   
02.//参数2是返回的UCS-2编码的Unicode字符   
03.inline int UTF82UnicodeOne(const char* utf8, wchar_t& wch)  
04.{  
05.    //首字符的Ascii码大于0xC0才需要向后判断,否则,就肯定是单个ANSI字符了   
06.    unsigned char firstCh = utf8[0];  
07.    if (firstCh >= 0xC0)  
08.    {  
09.        //根据首字符的高位判断这是几个字母的UTF8编码   
10.        int afters, code;  
11.        if ((firstCh & 0xE0) == 0xC0)  
12.        {  
13.            afters = 2;  
14.            code = firstCh & 0x1F;  
15.        }  
16.        else if ((firstCh & 0xF0) == 0xE0)  
17.        {  
18.            afters = 3;  
19.            code = firstCh & 0xF;  
20.        }  
21.        else if ((firstCh & 0xF8) == 0xF0)  
22.        {  
23.            afters = 4;  
24.            code = firstCh & 0x7;  
25.        }  
26.        else if ((firstCh & 0xFC) == 0xF8)  
27.        {  
28.            afters = 5;  
29.            code = firstCh & 0x3;  
30.        }  
31.        else if ((firstCh & 0xFE) == 0xFC)  
32.        {  
33.            afters = 6;  
34.            code = firstCh & 0x1;  
35.        }  
36.        else  
37.        {  
38.            wch = firstCh;  
39.            return 1;  
40.        }  
41.  
42.        //知道了字节数量之后,还需要向后检查一下,如果检查失败,就简单的认为此UTF8编码有问题,或者不是UTF8编码,于是当成一个ANSI来返回处理   
43.        for(int k = 1; k < afters; ++ k)  
44.        {  
45.            if ((utf8[k] & 0xC0) != 0x80)  
46.            {  
47.                //判断失败,不符合UTF8编码的规则,直接当成一个ANSI字符返回   
48.                wch = firstCh;  
49.                return 1;  
50.            }  
51.  
52.            code <<= 6;  
53.            code |= (unsigned char)utf8[k] & 0x3F;  
54.        }  
55.  
56.        wch = code;  
57.        return afters;  
58.    }  
59.    else  
60.    {  
61.        wch = firstCh;  
62.    }  
63.  
64.    return 1;  
65.}  


有了这个函数,那么转换一整个UTF8字符串,就是很简单的一件事情了,下面直接给出了最短的实现:

//参数1是UTF8编码的字符串
//参数2是输出的UCS-2的Unicode字符串
//参数3是参数1字符串的长度
//使用的时候需要注意参数2所指向的内存块足够用。其实安全的办法是判断一下pUniBuf是否为NULL,如果为NULL则只统计输出长度不写pUniBuf,这样
//通过两次函数调用就可以计算出实际所需要的Unicode缓存输出长度。当然,更简单的思路是:无论如何转换,UTF8的字符数量不可能比Unicode少,所
//以可以简单的按照sizeof(wchar_t) * utf8Leng来分配pUniBuf的内存……
int UTF82Unicode(const char* utf8Buf, wchar_t *pUniBuf, int utf8Leng)
{	
	int i = 0, count = 0;
	while(i < utf8Leng)
	{
		i += UTF82UnicodeOne(utf8Buf + i, pUniBuf[count]);
		count ++;
	}

	return count;
}


搞定了UTF-8转Unicode之后,反过来搞定Unicode转UTF8也是很容易的,下面直接给出单个Unicode转UTF8编码的函数:

 

inline int Unicode2UTF8(unsigned wchar, char *utf8)
{
	int len = 0;
	if (wchar < 0xC0)
	{ 
		utf8[len ++] = (char)wchar;
	}
	else if (wchar < 0x800)
	{
		utf8[len ++] = 0xc0 | (wchar >> 6);
		utf8[len ++] = 0x80 | (wchar & 0x3f);
	}
	else if (wchar < 0x10000)
	{
		utf8[len ++] = 0xe0 | (wchar >> 12);
		utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);
		utf8[len ++] = 0x80 | (wchar & 0x3f);
	}
	else if (wchar < 0x200000) 
	{
		utf8[len ++] = 0xf0 | ((int)wchar >> 18);
		utf8[len ++] = 0x80 | ((wchar >> 12) & 0x3f);
		utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);
		utf8[len ++] = 0x80 | (wchar & 0x3f);
	}
	else if (wchar < 0x4000000)
	{
		utf8[len ++] = 0xf8 | ((int)wchar >> 24);
		utf8[len ++] = 0x80 | ((wchar >> 18) & 0x3f);
		utf8[len ++] = 0x80 | ((wchar >> 12) & 0x3f);
		utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);
		utf8[len ++] = 0x80 | (wchar & 0x3f);
	}
	else if (wchar < 0x80000000)
	{
		utf8[len ++] = 0xfc | ((int)wchar >> 30);
		utf8[len ++] = 0x80 | ((wchar >> 24) & 0x3f);
		utf8[len ++] = 0x80 | ((wchar >> 18) & 0x3f);
		utf8[len ++] = 0x80 | ((wchar >> 12) & 0x3f);
		utf8[len ++] = 0x80 | ((wchar >> 6) & 0x3f);
		utf8[len ++] = 0x80 | (wchar & 0x3f);
	}

	return len;
}


参数1是一个Unicode编码,参数2为输出的UTF8编码。这里参数1使用了unsigned类型,不是wchar_t类型,仅仅是按照UTF8编码规则可能达到的最大范围来写而已。一个Unicode编码会输出多少个char字符,函数的返回值明确的给出了。所以原则上,参数2至少应该是一个char buf[6]的数组。至于一个Unicode字符串转UTF8字符串的转换代码,这里就不贴了,实在是简单,不浪费文字了。


现在开始就是不好做的GBK转Unicode了。众所周知GBK编码使用其实非常广泛,绝大多数的文本编辑软件,像Editplus,WIndows自带的记事本等等,如果你不设置其编码,那么默认基本上就是GBK,这个东西使用太广泛了,广泛得我们都快忘了他了。在Windows的API里,MultiByteToWideChar转换的时候,参数1为CP936,意思就是输入的多字节字符串为GBK编码,我们可以简单的认为这个GBK和CP936字符串是一回事(当然其实不能绝对的这样讲,毕竟不是一个组织定下来的东西,我们只是从技术上简单的这样认为他们是一样的就可以了)。而这个GBK转Unicode编码,难就难在,不像UTF8转Unicode一个算法就可以搞定了,GBK转Unicode只能通过查表来实现,Unicode转GBK也一样,只能通过查表来实现。


这下问题就复杂了,因为要查表就意味着GBK与Unicode编码之间虽然是一一对应关系,但不是简单的高位乘以多少加上低位乘以多少就能算出来的。更糟糕的是GBK与Unicode之间并不是连续的编码,中间总有空码,而且这个空码的情况,还不是特别的有规律。


不过好在微软公开了CP936与自家UCS-2的对照文本,基本这个文本,就可以很容易的看到每一个GBK编码的字符与Unicode码之间的对应关系了。这就好办了,我写了一个小程序,将这个文本文件读入,解析,然后输出一段C语言代码,其实就是一个大数组,在转换的时候,拿GBK码去这个数组里,就可以得到Unicode编码了。而且这个数组也不算大,因为最多只有65536个编码,也就是64*2=128Kb的Table。反过来Unicode到GBK,也同样是一个short型的table,也是128Kb。这个程序增长量还是可以接受的。如果觉得不爽的话, 也可以写在文本文件里,第一次运行的时候将文本载到内存里…………不过我觉得这样其实没有什么区别。


好了,废话少说了,下面是GBK转Unicode和Unicode转GBK编码的查表函数,文章的最后面是两个我导出的C语言代码的表文件链接,在下面的这两个函数之前用#include包进来就可以了。

//参数1是输入的Unicode字符串
//参数2是输出的GBK字符串
//参数3是输入字符串的长度
//返回值是输出GBK字符串的长度
int Unicode2GBK(const wchar_t* wchar, char *gbkBuf, int wcharLeng)
{
	int outLeng = 0;
	uchar* pWrite = (uchar*)gbkBuf;
	for(int i = 0; i < wcharLeng; ++ i)
	{
		wchar_t c = wchar[i];
		if (c <= 0x7F)
		{
			//小于0x7F,这是一个ANSI码,所以不用查表了
			outLeng ++;
			*pWrite ++ = c;
		}
		else if (c == 0x20AC)
		{
			//一个特殊字符,没有编进表里,所以在这里单独处理了
			*pWrite ++ = 0x80;
			outLeng ++;
		}
		else
		{
			//剩下的,就需要查表了,减去128,直接去表里查。Unicode转GBK的好处是只有一张线性表,一次就可以查到
			unsigned short ss = unicode2gbkTable[c - 128];
			*pWrite ++ = ss >> 8;
			*pWrite ++ = ss & 0xFF;
			outLeng += 2;
		}
	}

	return outLeng;
}

//参数1是输入的GBK字符串
//参数2是输出的Unicode字符串
//参数3是输入字符串的长度
//返回值是输出Unicode字符串的长度
int GBK2Unicode(const char* gbkBuf, wchar_t *pszBuf, int gbkLeng)
{
	int outLeng = 0;
	const uchar* pSrc = (const uchar*)gbkBuf;
	wchar_t* pWrite = pszBuf;
	for(int i = 0; i < gbkLeng; ++ i)
	{
		uchar ch = pSrc[i];
		if (ch <= 0x7F)
		{
			//ANSI字符
			*pWrite ++ = ch;
		}
		else if (ch == 0x80)
		{
			//特殊字符
			*pWrite ++ = 0x20AC;
			++ i;
		}
		else
		{
			//剩下的,就需要查表了,高位减128,得到这一段字符的表。整个Unicode转GBK一共分了几十个表,gbk2unicodeTables则记录了这些表的地址
			//所以先要按高位得到表地址,再用低位去该表里查找字符
			++ i;
			ch -= 0x81;
			if (ch < sizeof(gbk2unicodeTables) / sizeof(gbk2unicodeTables[0]))
			{
				const unsigned short* pTable = gbk2unicodeTables[ch];
				ch = pSrc[i];
				if (ch < 255)
					*pWrite ++ = pTable[ch - 0x40];
				else
					*pWrite ++ = 0;
			}
			else
			{
				*pWrite ++ = 0;
			}
		}

		outLeng ++;
	}

	return outLeng;
}


 

现在搞定了UTF8和Unicode之间的互相转换,也搞定了GBK和Unicode之间的互相转换,那么GBK和UTF8之间呢?呵呵,学学Windows的API,先将GBK转成Unicode,然后将Unicode再转成UTF-8,反过来亦然。

GBK和Unicode互转的字符码表文件
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值