• Qt学习(11)


    Qt学习(11)—字符编码方式

     

            在开始具体的窗体、控件、布局等图形界面编程之前,先学习一下字符编码方式和字符串的使用。字符串是图形界面的基石,虽然控件和图标看起来直观,但还是必定要使用字符串来显示信息的。 源代码文件里的字符编码通常有 ANSI 多字节编码和 Unicode 系列编码,字符编码影响程序运行时的文字显示, 如果处理不好就容易乱码。本节对操作系统里的字符编码进行简单介绍,这对于程序的跨平台开发、汉字乱码问题解决等都是很重要的知识,了解这些知识就不再害怕遇到程序乱码的问题了,本节最后测试不同字符编码的源文件的编译运行情况。

    1、ANSI多字节编码

            在最早的时期,计算机只支持英文字符,那时都是用 ASCII(American Standard Code for Information  Interchange,美国标准信息交换代码)编码,一个字母或符号只需要一个字节存储。随着计算机的推广应用,越来越多的国家和地区面临本地语言文字如何在计算机里使用和显示的问题。对于中文 DOS 系统和早期的中文 Windows 系统,大陆制定了国标码 GB2312,台港澳地区则使用了大五码 Big5。微软针对这些本地化字符编码采用的就是用 ANSI(American National Standards  Institute,美国国家标准学会)多字节编码方式,系统里的英文和符号就使用单字节的 ASCII(0x00~0x7f),而对于汉字之类的本地化字符编码,就采用 0x80~0xFF 范围内的多个字节来表示,这样既能兼容 ASCII ,又能正常使用本地化语言文字。大陆的国标码发展了好几代,归结如下:

    • GB2312:1980年发布,收录了7445个字符,包括6763个汉字和682个其它符号。汉字是双字节编码。
    • GBK:1995年发布,收录了21886个符号,包括21003个汉字和883个其它符号。汉字是双字节编码。简体中文 Windows目前默认采用这种本地化编码。
    • GB18030-2000:2000年发布,收录了27533个汉字,汉字分为双字节编码部分和四字节编码部分。
    • GB18030-2005:2005年发布,收录了70244个汉字,汉字也分为双字节编码部分和四字节编码部分。

             新的国标码标准通常是兼容旧的编码方式的,所以一般对简体中文的文本选择 GBK 或 GB18030 编码都是可以正常显示的。微软针对各种本地化语言的页面有自己的编号方式,GBK 对应的代码页编号是 936,GB18030 对应的代码页编号是54936,Big5 对应的代码页编号是 950。
            关于汉字编码可以参考: http://www.fmddlmyy.cn/text24.html

            关于代码页编号可以参考: http://zh.wikipedia.org/wiki/%E4%BB%A3%E7%A0%81%E9%A1%B5

    2、Unicode系列编码

          ANSI 多字节编码解决了各种语言文字的本地化使用问题,也有它自己的缺陷:各地制定的编码标准只对自己的语言文字有效,而不同语言文字的编码都是冲突的,因为大家都用 0x80~0xFF 范围字节表示自己的语言文字,而不考虑别的语言文字如何编码,冲突在所难免。比如简体中文(GBK)的文本放到繁体中文(Big5)的操作系统里,就被默认解析成繁体字编码,两种编码是冲突的,就会显示混乱的繁体字,反过来也一样。因此国际组织制定了 Unicode 编码,也叫万国码、国际码等,这种字符编码是对全球语言统一分配编码区间,各种语言字符互相不冲突,都可以兼容使用。Unicode 编码从最初的 1.0 ,慢慢发展到今年发布的 8.0,包含的语言文字越来越多。
            Unicode 编码系统,可分为编码方式和实现方式两个层次。对于国际组织发布的 Unicode 编码标准,对应的就是编码方式,最常用的是 UCS-2(Universal Character Set 2),采用两字节编码一个字符。当然国际语言文字太多,两字节不够用了,就有四字节编码方式 UCS-4。这个仅仅是标准,而不是实现,在编码实现的过程中,有些考虑兼容旧的单字节 ASCII 编码,有些不考虑兼容性;有些考虑双字节中哪个字节放在前面,哪个字节放在后面的问题,即 BOM(Byte Order  Mark,字节顺序标记)的作用。因此诞生了多种国际码的实现方式,统称为 Unicode 转换格式(Unicode Transformation  Format,UTF):

    • UTF-8:灵活的变长编码,对于 ASCII 使用一个字节编码,其他本地化语言文字用多个字节编码,最长可以到 6 个字节编码一个字符。对于汉字,通常是 3 个字节表示一个汉字。这是 Unix/Linux 系统默认的字符编码。
    • UTF-16:兼容 UCS-2,一般都是两字节表示一个字符,对于超出两字节的国际码字符,使用一对两字节来表示。在存储时,按两个字节的排布顺序,可以分为 UTF-16LE(Little Endian,小端字节序)和UTF-16BE(Big Endian,大端字节序),微软所说的 Unicode 默认就是 UTF-16LE。
    • UTF-32:同 UCS-4,因为用四个字节表示一个字符,所以不需要考虑扩展了。这种编码方式简单,但也特别浪费空间,所以应用很少。在存储时也分为 UTF-32BE 和 UTF-32LE,因为用得少,所以不用太关心这种编码格式。

           关于 Unicode 编码格式参考: http://baike.baidu.com/view/40801.htm

           关于 UTF 和 BOM 参考: http://www.unicode.org/faq/utf_bom.html

    3、C++使用的字符串

            在C++中,以前通常使用char表示单字节的字符,使用wchar_t表示宽字符,对国际码提供一定成都的支持。char*字符串有专门的封装类std::string来处理,标准输入输出流是std::cin和std::cout。对于wchar*字符串,其封装类型是std::wstring,其标准输入输出流是wcin和wcout。虽然规定了宽字符,但是并没有明确一个宽字符占用几个字节,Windows系统里的宽字符是两个字节,就是UTF-16;而Unix/Linux系统里为了更加全面的支持国标码,其宽字符是四个字节,即UTF-32编码。这为程序的跨平台带来了一定的混乱,除了Windows程序开发常用wchar_t*字符串表示UTF-16,其他情况下wchar_t*都用的比较少。

            MFC一般用自家的TCHAR和CString类支持国际化,当没有定义_UNICODE宏时,TCHAR = char,当定义了_UNICODE宏时,TCHAR = wchar_t,CString内部也是类似的。Qt则用QChar和QString类(内部恒定为UTF-16),一般的图形开发库都用自家的字符串类库。

            在新标准C++ 11中,对国际码的支持做了明确规定:

    • char * 对应 UTF-8 编码字符串(代码表示如 u8"多种文字"),封装类为 std::string;
    • 新增 char16_t * 对应 UTF-16 编码字符串(代码表示如 u"多种文字"),封装类为 std::u16string ;
    • 新增 char32_t * 对应 UTF-32 编码字符串(代码表示如 U"多种文字"),封装类为 std::u32string 。

    因为 Qt 有封装好的 QString,所以不太需要这些新增的字符串格式。关于 C++11 字符串更详细的内容参考:

    http://blog.poxiao.me/p/unicode-character-encoding-conversion-in-cpp11/

    4、源文件字符编码测试

            在 Windows 系统里最常用的文本字符编码格式是 ANSI (简体是 GBK,繁体是 Big5)和 Unicode (UTF-16LE)格式,Windows 命令行默认的输入输出格式是 ANSI 的。在 Linux 系统里统统都是 UTF-8 ,这个比较省心。Windows 自带的记事本,以及 Notepad2 工具、Linux 系统里的 kwrite、gedit 等都支持各种编码格式的文本显示。以 Windows 记事本为例,打开任意一个文本文件,选择菜单“文件”-->“另存为”,点开“另存为”对话框最下面的“编码”:

    图上四种格式分别是:

    • ANSI:本地化语言文字编码,简体操作系统是 GBK,繁体操作系统就是 Big5,Windows 命令行默认用的字符编码是和这个一样的。
    • Unicode:Windows 所说的国际码一般都指这个 UTF-16LE,文件开头带有 BOM 标记(2字节)。
    • Unicode big endian:即 UTF-16BE,这个很少用到,文件开头也带 BOM 标记(2字节)。
    • UTF-8:这是 Unix/Linux 系统里通用的国际码存储交换格式,记事本保存的文件开头会有 UTF-8 签名(3字节,类似 BOM)。

           下面我们测试三个不同编码格式的 cpp 文件,查看其编译运行效果。打开本教程示例代码的网址,进入ch03/textcodec子文件里面,下载里面的压缩包 textcodec.7z,解压缩到 E:QtProjectsch03 extcodec 文件夹里。  解压后可以看到 GBK.cpp、UTF-8.cpp、UTF-16.cpp 三个文件,如文件名所述,第一个是 ANSI+GBK 编码的源文件,第二个是 UTF-8 编码的源文件,第三个是UTF-16LE(Windows默认的Unicode)编码的源文件。用记事本打开查看它们的内容是完全一样的:

    #include <iostream>
    #include <cstring>
    
    using namespace std;
    
    
    int main(int argc, char *argv[])
    {
        char theStr[] = "1234打印汉字";
        cout<<"str length: "<<strlen(theStr)<<endl;
        cout<<theStr;
        
        return 0;
    }

    注意看文件存储的字节数:

    • GBK.cpp 是 231 字节,231 = 2*4(汉字) + 223 (其他英文和符号);
    • UTF-8.cpp 是 238 字节,238 = 3*4(汉字)+ 3(文件头签名)+ 223 (其他英文和符号);
    • UTF-16.cpp 是 456 字节,456 = 2*4(汉字)+ 2(BOM)+ 223*2 (其他英文和符号)。

        然后我们在 Qt 命令行尝试用 g++ 编译这些代码,并运行生成的程序,挨个测试:

        g++ GBK.cpp -o GBK

     g++ UTF-8.cpp -o UTF-8

    g++ UTF-16.cpp -o UTF-16

     

            第一个 GBK 编码的程序是正常的,那是因为作者使用的 Windows 命令行默认的代码页是 936 ,命令行里的输入输出都是 GBK 编码的,与源文件编码格式一致,所以显示正确。
            第二个 UTF-8 正常编译,但是源文件里的 UTF-8 汉字编码与 Windows 命令行代码页编码格式不符合,所以显示的汉字乱码,而数字是没问题的。UTF-8 和 GBK 都是兼容 ASCII 单字节编码的。
            第三个 UTF-16 编译根本不成功,因为 UTF-16 里面一个字符用双字节表示,导致源文件有大量的数值为 0 的字节(被编译器认为是null),因此编译失败了。这也是 UTF-16 不兼容 ASCII 单字节编码的弊端。

            如果在 Linux 系统里用 g++ 编译上面三个文件,结果是 GBK 编码程序可以编译,运行乱码;UTF-8 编码程序可以编译,运行正常;UTF-16 编码程序是无法通过编译的。Linux 系统里命令行默认的输入输出都是 UTF-8 编码,所以只有 UTF-8 的程序才会正常。GBK 的能正常编译是因为兼容 ASCII 单字节编码的缘故,无论是英文变量名还是符号,都与 UTF-8 的编码一致,只有非 ASCII 字符才会显示不正常。 

             那么 UTF-16 的源代码谁能正常编译呢?答案就是微软出的 Visual Studio,微软自家用的 Unicode 还得靠 MSVC 编译器来编译:cl UTF-16.cpp /EHsc


            可以看到 MSVC 编译器是能正常编译 UTF-16 的源文件,显示也很正常。感兴趣的读者可以用 MSVC 编译器(VS2010  SP1以上版本)把三个源文件都测试一遍,可以发现 MSVC 编译器生成的 exe 运行都是正常显示汉字的,这是为什么呢?
           可以大致理解为:
    ①g++ 编译器不会对源文件里的字符串做转码。上面 g++ 生成的 GBK.exe 与 UTF-8.exe 二进制文件内容是有多处差异的。在 Windows 系统里源文件是 GBK ,其命令行编码也是 GBK,所以 GBK 编码的程序正常显示汉字;在 Linux 系统里源文件是 UTF-8,其命令行编码也是 UTF-8,所以 UTF-8 编码的程序正常显示汉字。

    ②MSVC 编译器会根据不同的源文件编码格式进行转码,文件中 char * 字符串一律转成本地化的 ANSI 多字节编码, wchar_t *  字符串一律转成 Unicode(UTF-16LE)。用 MSVC 生成的 GBK.exe、UTF-8.exe、UTF-16.exe 三个二进制文件,除了 PE 文件头有几字节区别,程序主体就是完全一样的,这三个 exe 内部的字符串全是 ANSI 多字节编码的,当然都能正常显示汉字。

            从跨平台运行的角度,我们推荐 UTF-8 编码的源文件,这也是 Qt5 官方规定的源文件格式。下一节我们介绍 Qt 程序里的字符编码函数,无论是打印到操作系统命令行还是 Qt Creator 的输出显示面板,我们都可以让汉字正常显示。  

     

     

     

  • 相关阅读:
    @codeforces
    @atcoder
    @loj
    @atcoder
    @atcoder
    @loj
    @atcoder
    @atcoder
    @atcoder
    @uoj
  • 原文地址:https://www.cnblogs.com/wyxsq/p/5067009.html
Copyright © 2020-2023  润新知