IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Windows API 字符编码转换以及一些解释和心得

    Shihira发表于 2013-10-28 14:49:00
    love 0

    我在解决乱码上面实际走了不少弯路,做了很多实验,查了很多资料。在这里做下笔记,希望后来者可以明白,少走些弯路。

    从最熟悉的两种字符编码说起

    除了一些旧的、没有考虑到兼容性的网页还在用gbk做编码外,大部分的网页都已经用utf-8做编码了。但是最令人头疼的是,windows的控制台是很不好显示utf-8的。有明君为我大C++写了两个函数,是正确的、好用的(除了用std::string做返回值让我等效率党有点觉得不爽之外……还是挺方便的).

    #include <string>
    #include
    <windows.h>
    using std::string;

    //gbk 转 utf8
    string GBKToUTF8(const string& strGBK)
    {
    string strOutUTF8 = "";
    WCHAR
    * str1;
    int n = MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, NULL, 0);
    str1
    = new WCHAR[n];
    MultiByteToWideChar(CP_ACP,
    0, strGBK.c_str(), -1, str1, n);
    n
    = WideCharToMultiByte(CP_UTF8, 0, str1, -1, NULL, 0, NULL, NULL);
    char * str2 = new char[n];
    WideCharToMultiByte(CP_UTF8,
    0, str1, -1, str2, n, NULL, NULL);
    strOutUTF8
    = str2;
    delete[]str1;
    str1
    = NULL;
    delete[]str2;
    str2
    = NULL;
    return strOutUTF8;
    }

    //utf-8 转 gbk
    string UTF8ToGBK(const string& strUTF8)
    {
    int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8.c_str(), -1, NULL, 0);
    unsigned
    short * wszGBK = new unsigned short[len + 1];
    memset(wszGBK,
    0, len * 2 + 2);
    MultiByteToWideChar(CP_UTF8,
    0, (LPCTSTR)strUTF8.c_str(), -1, wszGBK, len);

    len
    = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
    char *szGBK = new char[len + 1];
    memset(szGBK,
    0, len + 1);
    WideCharToMultiByte(CP_ACP,
    0, wszGBK, -1, szGBK, len, NULL, NULL);
    //strUTF8 = szGBK;
    std::string strTemp(szGBK);
    delete[]szGBK;
    delete[]wszGBK;
    return strTemp;
    }

    这玩意儿不跨平台,因为它用到了windows api。我之所以把它放到跨平台编程上面来,是因为字符编码这东西只有到跨平台的时候才显得坑爹。


    接着我是不是要介绍那俩函数一下?

    int MultiByteToWideChar(
    _In_ UINT CodePage,
    /*代码页是Windows下字符编码的叫法,gbk是936,utf-8是65001,CP_ACP是ANSI*/
    _In_ DWORD dwFlags,
    /*选项标志,转换类型,设0就行了*/
    _In_ LPCSTR lpMultiByteStr,
    /*多字节字符串*/
    _In_
    int cbMultiByte, /*字符串要处理的长度,如果是-1函数就会处理整个字符串*/
    _Out_opt_ LPWSTR lpWideCharStr,
    /*输出的宽字符串缓存,如果为空就返回需要的宽字符串长度*/
    _In_
    int cchWideChar /*宽字符串缓存的长度,当然如果宽字符串为空,这个设0就可以了*/
    );

    int WideCharToMultiByte(
    _In_ UINT CodePage,
    _In_ DWORD dwFlags,
    _In_ LPCWSTR lpWideCharStr,
    _In_
    int cchWideChar,
    _Out_opt_ LPSTR lpMultiByteStr,
    _In_
    int cbMultiByte, /*前面的基本与MultiByteToWideChar都相同,就不解释了*/
    _In_opt_ LPCSTR lpDefaultChar,
    /*填0即可*/
    _Out_opt_ LPBOOL lpUsedDefaultChar
    /*填0即可*/
    );

    这两个函数分别是将多字节字符串转换为宽字符字符串 和 将宽字符字符串转换为多字节字符串(在此处晕倒的童鞋们我没有对不起你们……是M$那家伙对不起你们)。我早就说过Windows API 的界面不友好,这么多不知道干嘛吗用的参数,全部填0就对了。要是iconv(),它貌似只有4个参数,这才是好的榜样。


    宽字符?多字节?

    这是Windows给它们起的名字,让人摸不着头脑。

    • 宽字符:就是Unicode。它雷打不动地用2个字节(0x0000 - 0xFFFF),表示所有我们平常能见到的字符,具体的表格见:http://unicode-table.com

    • 多字节:就是除了Unicode外其他的。我们熟悉的gbk, utf-8, big5,统统归入多字节。

    宽字符之所以叫做宽字符,是因为它是一个宽一点的字符。那什么是短字符……就是ascii了,1个字节1个字符绝对够短,而且只能表示256个西欧字符。宽字符呢,是2个字节1个字符。宽一点,但还是可以识别到一个字符是哪里的。而多字节呢,就是它在计算机里表示成多个字节,但是没有办法识别那里到那里是一个字符。

    我不喜欢这两个函数的命名。如果按照Python的命名,MultiByteToWideChar 应该叫 decode(解码),WideCharToMultiByte 应该叫 encode(编码)。


    所以呢?

    如你所见,多字节无法准确识别字符的长度,处理起来就会很麻烦。而宽字符大多时候虽然比多字节多耗费一点空间,但是处理起来方便。比如正则表达式处理,引擎是基于字符去匹配的,宽字符可以两个字节两个字节跳着匹配,而多字节就会匹配错误。

    比如有一个词“程序”=0xB3CCD0F2(gbk),我想匹配“绦”=0xCCD0(gbk),正则库会替我把中间那两个字节匹配了。用在C里用wchar_t,C++里用std::wstring,我们可以很准确的,无错误地匹配到我们想要的子串,因为引擎在迭代的时候是逐字(而不是逐字节)进行比较的。

    1 >>> str1 = "绦"
    2 >>> str2 = "程序"
    3 >>> print re.findall(str1, str2)
    4 ['\xcc\xd0']
    5 >>> print re.findall(str1.decode("gbk"), str2.decode("gbk"))
    6 []

    所以在处理字符串的时候,但凡要处理中文,要先把用户给的字符串解码成Unicode。处理完之后显示出来或者保存,再编码成需要的charset。


    Appendix

    在不同的地方用不同的编码:

    • 网络文本(如网页)传输一般用utf-8,因为有少量中文,而大部分是英文。
    • 在保存为本地文件的时候,应该保存为Unicode,因为本地存储资源丰富,且可以节省时间,实时解码毕竟也是O(N^2)啊。
    • 显示出来应该用系统的编码,中文Windows为gbk,繁体Windows为Big5,Linux一律为UTF-8。
    • 源代码里的少量中文串尽量用"\x????\x????"来表示,如果有大量中文建议用gettext或者资源之类的以外挂的方式读入。
    • Qt内部使用Unicode,所以编写Qt应用时显示文字直接传递宽字符串即可。
    • NTFS的文件名、路径都是用GBKUTF16LE编码的,所以如果Windows下用户输入的是路径就无需解码了。




    Shihira 2013-10-28 22:49 发表评论


沪ICP备19023445号-2号
友情链接