zoukankan      html  css  js  c++  java
  • 你需要知道的编码(乱码)知识

     

    你需要知道的编码(乱码)知识

    中文在风靡全球的路上如果一定有阻碍,那就是乱码啊。引无数大神尽折腰的编码转换问题,这篇文章就记录下这个问题。

    大家知道,计算机是只认识二进制的,如果一个字符变成了我们肉眼可见的乱码时,一定是因为我们给了计算机错误的编码格式导致的。

    文件编码

    文章开始,我们先说说编程时,我们的保存代码的文件的编码,以VS2008为例,在中文操作系统下,我们的编程文件会被保存为GB2312编码,但凡文件中使用了中文,为了正常显示中文,我们必须使用扩展的编码方式来显示这个文件。GB2312算是最早的中文编码方式了,后面依次出了GBK和GB18030,这些暂且不提,编码参考可以看这里。GB2312使用两个字节表示一个中文字符,而UTF-8则使用3个字节表示一个中文。如果不想看文献,我们可以走一个实验:

    #include <iostream>
    
    //this file for coder test
    #include <string>
    using std::cout ;
    using std::string;
    
    int main()
    {
    string ansiByte = "this";
    string chineseByte = "这是中文";
    cout << "english lenght:" << ansiByte.length() <<std::endl;
    cout << "chinese lenght:" << chineseByte.length() <<std::endl;
    
    return 0;
    }

    分别把以上代码,以GB2312和Utf-8保存后,生成的结果分别为:

    english lenght:4
    chinese lenght:8
    

      

    english lenght:4
    chinese lenght:12
    

      

    以上结果很清楚的证明了以上说法。当然了,英文字符是编码界的一等公民的,所以编码方式变化基本不会影响代码最后的结果。而对于中文,我们就很尴尬了,毕竟string的length函数不可用的情况下,对应的c语言里的strlen函数也是不准确的。这个问题 ,我们在后面说说如何解决。现在,我们还是说回文件的编码问题,因为WINDOWS的文本文件换行格式与LINUX不同的原因,WINDOWS行尾使用 来换行,LINUX行尾使用 来换行, 在LINUX就会显示成奇怪的符号。这样的系统级的差异,我们不会扩展,只以windows系统说明后面的问题。

    不同的文件编码在转换时,必然会引起乱码问题,这里的乱码是因为错误的字符映射关系导致的。类似的说明网文也是很多的。

    这里只说明一点,为使计算机支持更多语言,兼容ASCII码表,在0x00~0x7F之间的字符,依旧是1个字节代表1个字符。通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个非英语字符。像GB2312, BIG5, JIS 等使用ANSI码表的0x80~0xFF范围的 2 个字节来代表一个字符的各种汉字延伸编码方式,统称为ANSI 编码。因此,在简体中文系统中,ANSI编码实际上是指GBK(GB2312或者GB18030).

    字符编码

    开发者除了需要处理文件的编码导致的乱码外,还需要处理编码过程中,网络传输时的乱码问题。在开发过程中,我们经常遇到TCHAR来表示一个字符字节,众所周知的原因,这里其实隐藏了char和wchar_t类型,即所谓的窄字节 和 宽字节,一个char对应1个8bit,而wchar_t又存在平台差异,windows下是2个8bits,对应一个UTF-16编码字符(Linux下是4个8bit,对应一个UTF-32字符),Windows下的wchar_t定义为 typedef unsigned short wchar_t; /* 16 bits */ 在编码时,我们使用L来表示我们使用的是Unicode编码,但是wchar_t也可以存储其他编码的字符,即如果我们不加L标识的话,这个字符就是我们的编程文件的编码格式,但是在linux下我们无法使用不加L的字符串给wchar_t类型赋值了。来,show me the code:

    wchar_t width_t1 = ''; 
    wchar_t width_t2 = L''; 
    printf( "%0x %0x
    ",width_t1,width_t2);

    文件编码为GB2312时的输出

    d6d0 4e2d

    文件编码为UTF-8时的输出

    e4b8ad 4e2d

    由此可见,当使用L修饰的中文后, 因为它已经表明使用了特定的编码方式,所以它在内存中的数值是一致的,不会随着文件的编码而改变,但是不加入L则就与文件的编码格式相关了。现在,我们已经积累了2个随着文件编码方式不同,导致程序输出不同的问题了,这些都是乱码的根源。

    乱码回归

    为了弄清楚乱码的问题,我们从文件的编码方式,编程方式来说明了潜在的问题,现在,我们来看看可以从哪些方面解决这个问题。

    • 文件编码

    为了正常显示中文,我们可以将文件编码设置为GB2312的,但是现在很多IDE把字符编码默认为UTF-8了比如Qt的IDE ,QtCreator。如前所述,UTF-8存储中文时占用的磁盘空间比GB2312更大些,嗯,其实吧,存储空间并不是什么大问题。不同的文件编码其实影响的是我们在做网络数据的传输,我们需要根据不同的编码来转码为UTF-8了。

    备注说明下:在linux下,含中文的文件最好保存为UTF-8格式的,使用ANSI编码在编辑时可能会报"converting to execution character set: Invalid or incomplete multibyte or wide character";这需要做很多额外的工作来消除。

    • 使用严格的宽字节

    现在,我们假设,文件的编码格式为GB2312,而我们需要使用中文字符串了,使用string char等类型来存储中文字符是完全可以的,那么这里就会涉及到1个中文2个char字节的口诀了,古时候(嗯,那时候),即时现在,很多人都还没玩坏这个口诀。这自然不是武功秘籍,所以,我们需要改变下编码思路,因为,直接使用string存储中文,字符长度等函数都是不准确的。于是,我们使用了严格的宽字节来规避这些问题,VS中可以把字符设置为Unicode,这个设置不是为文件编码准备的,是为编程中的编码准备的。设置完毕后,我们将合理的使用TCHAT等字符类型。当字符设置为Unicode后,这里的TCHAR会被解析为wchar_t,个人其实并不喜欢微软的字符封装,所以更喜欢直接使用C++标准库中的wchar_t及其标准操作函数。show me the code:

    wchar_t width_t1[] = "中文1测试起来"; //error C2440: “初始化”: 无法从“const char[14]”转换为“wchar_t []”

    以上代码说明,vs下没有加L的中文串默认为chat类型,

    wchar_t width_t2[] = L"中文2测试起来"; 
    printf( "%s 
    ",width_t2);

    结果(powershell下显示)

    -N噀2

    又见乱码,喜不胜收啊,分析下,加了L后,我们的wchar中加入的为UTF-16的字符编码,而我们的显示界面为powershell的字符集为GBK的,把utf-16的字符解析为GBK自然就是乱码了。于是,就涉及到解码了,这里,我们的目标就是把wchar_t类型的字符转为ANSI编码,如果不知道为什么是这样的转换,可以多查看资料,其实,本文前面也略有提及,具体的转换在下节。

    wstring wchar1(L"this");
    wstring wchar2(L"只有中文");
    wstring wchar3(L"中e混合");
    
    std::cout << "e文长度 :" << wchar1.length()<<std::endl;
    std::cout << "中文长度 :" << wchar2.length()<<std::endl;
    std::cout << "混合长度 :" << wchar3.length()<<std::endl;
    e文长度 :4
    中文长度 :4
    混合长度 :4

    可见,在宽字节加持下,中英文的长度等总算统一了,所有的字符都是按一个计算,让一个中文是两个英文字符见鬼去吧,我们完全不需要关系在磁盘或者内存中字符的存储方式,因为我们更关心编程的字符串本身。

    • 编码格式转换

    在Windows下,编码转换主要是WideCharToMultiByte和MultiByteToWideChar函数在此之前,我们先来了解下代码页的概念,参考这里,知道了codepage的存在,我们就很好理解这两个函数了,这两个函数就是在查表,找到宽字节 到 多字节(ANSI)的对应关系;

    show me the code

    //函数名中的Unicode实际就是UTF-16,
    //需要加头文件 Windows.h
    CHAR * UnicodeToAnsi(const WCHAR * lpszStr)
    {
    CHAR * lpAnsi;
    int nLen;
    
    if (NULL == lpszStr)
    return NULL;
    //查找代码页,先获取宽字节到多字节时需要的byte长度
    nLen = ::WideCharToMultiByte(CP_ACP, 0, lpszStr, -1, NULL, 0, NULL, NULL);
    if (0 == nLen)
    return NULL;
    
    lpAnsi = new CHAR[nLen + 1];
    if (NULL == lpAnsi)
    return NULL;
    
    memset(lpAnsi, 0, nLen + 1);
    //执行映射转换,两次调用同样的函数,获取不同的值,总觉得 哪里不对,不过任务算是完成了
    nLen = ::WideCharToMultiByte(CP_ACP, 0, lpszStr, -1, lpAnsi, nLen, NULL, NULL);
    if (0 == nLen)
    {
    delete []lpAnsi;
    return NULL;
    }
    
    return lpAnsi;
    }
    char * charansi = UnicodeToAnsi(width_t2);
    printf( "%s 
    ",charansi);
    delete []charansi;

    执行后OK,这一次总算显示正常了。

    以上,告诉了你从wchar_t向ANSI转换的过程,那么同理,你可以写出其他的转换函数了,只是,知道为什么要转其实比知道怎么转更重要。

    • 网络通讯使用UTF-8编码

    UTF-8的地位是互联网给与的,而Unicode的推广是因为UTF-8,其中缘由自己百度咯。同样的道理,在联网通信时,为了让对方正确识别,我们需要把字符全部转成UTF-8,

    前文说过,我们需要使用宽字节来使用中文字符串,所以,这里我们只需要把宽字节的UTF-16转换为UTF-8即可。只有英文的可以不做转换。

    另外,本地化编程也是一个大坑,以后继续再填吧。

  • 相关阅读:
    CodeForces
    bzoj 2257: [Jsoi2009]瓶子和燃料
    【NOIP2009】Hankson 的趣味题
    51Nod 1203 JZPLCM
    bzoj 3751: [NOIP2014]解方程
    UOJ #11. 【UTR #1】ydc的大树
    Tenka1 Programmer Contest D
    bzoj 5000: OI树
    bzoj 1407: [Noi2002]Savage
    bzoj 3551: [ONTAK2010]Peaks加强版
  • 原文地址:https://www.cnblogs.com/Stultz-Lee/p/9979603.html
Copyright © 2011-2022 走看看