转自http://blog.csdn.net/nrc_douningbo/article/details/5880602
前端时间要自己实现utf8和unicode格式转换,打算封装一个类。也想过偷懒,直接网上找一个拿来用,但是后来发现问题多多。首先:接口不一样,其次:网上大部分方法还是不靠谱的,或者说:方法现在已经已经不适用了。
关于二者转换的原理网上很多,这里就不赘述了。主要说一下我遇到的问题:
我希望写成的接口是这个样子的:
- class CStrConvertor
- {
- public:
- static int Unicode2Utf8(LPSTR cBuf, int& iCBuf,LPCWSTR uBuf, int iUBuf);
- static int Utf82Unicode(LPWSTR pDst, int nDstLen , LPSTR pSrc,int nSrcLen);
- };
亦即:外边分配好存储转换结果的空间,而后在方法中对其进行赋值。
问题1:如何做参数有效性检查。检查集中在结果存储空间是否足够大小上。由于utf8是变长的,在转换结束之前永远不知道其具体占用多大空间,所以不好先期判断。可能也有人说了:Unicode2Utf8时我直接开3倍+1大小的空间,Utf82Unicode直接开2k+2的空间不就足够了?在转换函数中直接判断结果存储空间达没达到这个大小不就OK了?的确,这样没错。但是作为一个完善的类,这样是不严谨的!比如:Unicode2Utf8时可能提供的结果串只需要2k空间就足够了,外边也开了2倍空间,但是我们函数中上来就判断其不到3倍+1然后给报错了,这显然不行!所以:用最大空间来判断不好!
解决:1:网上也有人这么做,就是结果存储空间是在转换函数内部分配的,外部只是声明了一个指针而已。这样随着转换的进行,同时追加分配新空间! 这样的确解决了上边的问题,但是分配空间本身是相当消耗资源的,这函数的效率就低了!
2:我用的方法:在转换前也做一次遍历,看每个字符转换后占用几个byte,进行累加。依据最后的值判断参数的有效性!这样虽然给人感觉多做了一遍,但是较为严谨!
问题2:比如在Utf82Unicode时,我们需要看每个byte的前几位是否满足utf8的格式定义,这要用到:从一个字节中取出前/后若干位来。网上很多方法都是简单的直接做了个移位操作,比如:0xff<<5 。但是这样显然会遇到一个很基础的错误!
把问题抽象出来:0xff << 5 和 0xe0是一样的吗?嗯,有时候一样,有时候不一样!当直接使用<< 或>>进行位移操作时:你要明白它在cpu中是如何进行的。比如一个32位宽的cpu,当执行0xff << 5时:高位移除的5位并没有丢失掉!毕竟32位宽,所以移位完之后变为:0x1fe0 ,这也是为什么0xff << 5 和 0xe不一定相等的原因。二者相等不相等取决于cpu的位宽! 网上很多算法都没有注意到这点,我猜测那些算法都是8位宽时代实现的,后来放网上时也没去修改。但是这种不同只有在左移时才发生,右移时不会发生!这提示我们:在使用时尽可能少用移位操作符,本身考虑的东西太多而且代码阅读性也不好。
---------------------------------------------------------------------------------------------------------------------------------------------------
基础补充:
1:一个unicode在内存中占用2个byte。而utf是一种存储格式,其是变长的,最长可达6byte。但是基本上3个byte已经包括目前所能用到的全部字符了。 所以:Unicode2Utf8时最坏情况为:每个Unicode转完后都占用3个byte,所以最大要开3倍空间,再加上有一个/0结束符,所以最大需要开3k+1的空间! 而Utf82Unicode时:最坏情况下每个Utf8都转为一个unicode,亦即原本的每个byte先变为2个byte,再加上最后两个byte的/0 所以最大需要开2k+2的空间!
---------------------------------------------------------------------------------------------------------------------------------------------------
下边是我封装的类代码,有错误大家指正。
- /**
- * 该函数用于将Unicode数组转为utf8格式!
- * @param out 转换结果存放区域指针
- * @param outLength 转换结果存放区域大小
- * @param in 源串存放区域指针
- * @param inLength 源串存放区域大小
- * @return QINT 转换结果在目的串中实际使用的长度,转换失败则返回-1
- */
- int CStrConvertor::Unicode2Utf8( char* out, int& outLength,const wchar_t * in, int inLength )
- {
- //------------------------------------------------
- //参数有效性判断
- if(out == NULL || in == NULL || inLength<0)
- {
- return -1;
- }
- int totalNum = 0;
- for(int i = 0; i < inLength; i++)//计算转换结果实际所需长度
- {
- wchar_t unicode = in[i];
- if (unicode >= 0x0000 && unicode <= 0x007f)
- {
- totalNum += 1;
- }
- else if (unicode >= 0x0080 && unicode <= 0x07ff)
- {
- totalNum += 2;
- }
- else if (unicode >= 0x0800 && unicode <= 0xffff)
- {
- totalNum += 3;
- }
- }
- if( outLength < totalNum )//参数有效性判断!
- {
- return -1;
- }
- //------------------------------------------------
- int outsize = 0;//用来计数输出结果的实际大小!
- char *tmp = out;
- int i = 0;
- for (i = 0; i < inLength; i++)
- {
- if(outsize>outLength) //空间不足对应处理!
- {
- return -1;
- }
- wchar_t unicode = in[i];
- if (unicode >= 0x0000 && unicode <= 0x007f)
- {
- *tmp = (char)unicode;
- tmp += 1;
- outsize += 1;
- }
- else if (unicode >= 0x0080 && unicode <= 0x07ff)
- {
- *tmp = 0xc0 | (unicode >> 6);
- tmp += 1;
- *tmp = 0x80 | (unicode & (0xff >> 2));
- tmp += 1;
- outsize += 2;
- }
- else if (unicode >= 0x0800 && unicode <= 0xffff)
- {
- *tmp = 0xe0 | (unicode >> 12);
- tmp += 1;
- *tmp = 0x80 | (unicode >> 6 & 0x00ff);
- tmp += 1;
- *tmp = 0x80 | (unicode & (0xff >> 2));
- tmp += 1;
- outsize += 3;
- }
- }
- return outsize;
- }
- -------------------------------------------------------
- /**
- * 该函数用于将utf8数组转为Unicode格式!
- * 目前该函数返回值为:转换后unicode数据占用的wchar_t的个数(切记不是总char的个数) !
- * @param out 转换结果存放区域指针
- * @param outsize 转换结果存放区域大小
- * @param in 源串存放区域指针
- * @param insize 源串存放区域大小
- * @return QINT 转换结果在目的串中的长度,转换失败则返回-1
- */
- QINT CStrConvertor::Utf82Unicode(LPWSTR out, QINT outsize , LPSTR in,QINT insize)
- {
- //-------------------------------------------------------------------------------------------
- //参数有效性判断
- if(out == NULL || in == NULL || insize<0)
- {
- return -1;