3.7 Qt和字符集

3.7.1 计算机上的3种字符集

在计算机中每个字符都要使用一个编码来表示,而每个字符究竟使用哪个编码来表示要取决于使用哪个字符集(Charset)。

计算机字符集可归类为3种:单字节字符集(SBCS)、多字节字符集(MBCS)和宽字符集(Unicode字符集)。

(1)单字节字符集(SBCS)

单字节字符集的所有字符都只有一个字节的长度。单字节字符集(SBCS)是一个理论指导规范。具体实现时有两种字符集:ASCII字符集和扩展ASCII字符集。

ASCII字符集主要用于美国,是由美国国家标准局(ANSI)颁布的,全称是美国国家标准信息交换码(American National Standard Code For Information Interchange),使用7位(bit)来表示一个字符,总共可以表示128个字符(0~127),不过一个字节有8位,有1位没有用到,因此人们把最高1位永远设为0,用剩下的7位组成的编码来表示字符集的128个字符。ASCII字符集包括英文字母、数字、标点符号等常用字符,如字符'A'的ASCII码是65、字符'a'的ASCII码是97、字符'0'的ASCII码是48、字符'1'的ASCII码是49。其他字符编码的具体细节可以查看ASCII码表。

在美国刚刚兴起计算机的时候,ASCII字符集中的128个字符就够用了,一切应用都是顺顺当当的。后来计算机发展到欧洲,欧洲各个国家的字符较多,128个就不够用了,怎么办?人们对ASCII码进行了扩展,因此就有了扩展ASCII字符集。它使用8位来表示一个字符,即可表示256个字符,在前面0到127的编码范围内定义的字符与ASCII字符集中的字符相同,后面多出来的128个字符用来表示欧洲国家的一些字符,如拉丁字母、希腊字母等。有了扩展的ASCII字符集,计算机在欧洲的发展也就顺风顺水了。

(2)多字节字符集(MBCS)

随着计算机普及到更多国家和地区(比如东亚和中东),需要的字符就更多了,8位的单字节字符集不能满足信息这些国家和地区交流的需要。因此,为了能够表示更多国家和地区的文字(比如中文),人们对ASCII码继续扩展,也就是在欧洲人扩展的基础上再进行扩展,即英文字母和欧洲字符为了和扩展ASCII兼容,依然用1个字节表示字符,而对于更多国家和地区自己的字符(如中文字符)则用2个字节来表示,这就是多字节字符集(Multi-Byte Character System,MBCS),它也是一个理论指导规范,具体实现时各个国家或地区根据自己的语言字符分别实现了不同的字符集,比如中国大陆实现了GB-2312字符集(后来又扩展出GBK和GB18030)、中国台湾地区实现了Big5字符集,日本实现了jis字符集。这些具体的字符集虽然不同,但实现的依据都是MBCS,也就是字符编码256后面的字符都用2个字节来表示。

MBCS解决了欧美地区以外不同语言中字符的表示,但缺点也很明显。MBCS在保留原有扩展ASCII码(前面256个)的同时,用2个字节来表示其他语言中的字符,这样会导致一个字节和两个字节混在一起,使用起来不太方便。例如,字符串“你好abc”,字符数是5,而字节数是8(因为最后还有一个'\0')。对于用++或--运算符来遍历字符串的程序员来说,这简直就是噩梦。另外,各个国家或地区各自定义的字符集难免会有交集,比如使用简体中文的软件就不能在日文环境下运行(会显示出乱码)。

这么多国家或地区都定义了各自的多字节字符集,并以此来为自己的文字编码,那么操作系统如何区分这些字符集呢?操作系统通过代码页(Code Page)来为各个字符集定义一个编号,比如437(美国英语)、936(简体中文)、950(繁体中文)、932(日文)、949(朝鲜语_朝鲜)、1361(朝鲜语_韩国)等都是属于代码页。在Windows操作系统的控制面板中可以设置当前系统所使用的字符集。例如,通过控制面板打开Windows 7的“区域和语言”对话框,然后切换到“管理”选项卡,可以看到当前非Unicode(也就是多字节字符集)程序使用的字符集,如图3-6所示(在Windows 10中的设置界面与此类似)。

在图3-6中选定的语言是“中文(简体,中国)”,系统此时的代码页就是936。我们可以编写一个控制台程序验证一下。注意,控制台程序输出窗口默认使用的代码页(字符集)就是操作系统的代码页,也可以调用函数SetConsoleOutputCP()修改控制台窗口的代码页。这个函数虽是Windows API函数,但可以在Qt项目中使用。

图3-6 区域和语言

(3)Unicode字符集

Unicode编码被称为统一码、万国码或单一码。为了把全世界所有的文字符号都统一进行编码,标准化组织ISO提出了Unicode编码方案。这个编码方案可以容纳世界上所有文字和符号的字符编码,并规定任何语言中的任一字符都只对应一个唯一的数字。这个数字被称为代码点(Code Point),或称为码点、码位,用十六进制书写,并加上U+前缀,比如'田'的代码点是U+7530、'A'的代码点是U+0041。

所有字符及其Unicode编码构成的集合叫Unicode字符集(Unicode Character Set,UCS)。早期的版本有UCS-2,用两个字节进行编码,最多能表示65535个字符。在这个版本中,每个代码点的长度有16位(比特位),用0至65535之间的数字来表示世界上的字符(当初以为够用了),其中0至127这128个数字表示的字符依旧与ASCII码中的字符完全一样,比如Unicode和ASCII中的数字65都表示字母 'A'、数字97都表示字母 ' a'。反过来却是不同的,字符 'A'在Unicode中的编码是0x0041、在ASCII中的编码是0x41,虽然它们的值都是97,但是编码的长度是不一样的(Unicode码是16位长度,ASCII码是8位长度)。

UCS-2后来不够用了,又推出UCS-4版本。UCS-4用4个字节编码(实际上只用了31位,最高位必须为0),它根据最高字节分成27=128个组(最高字节的最高位恒为0,所以有128个组)。每个组再根据次高字节分为256个平面(Plane)。每个平面根据第3个字节分为256行(Row),每行有256个码位(Cell)。组0的平面0被称作基本多语言平面(Basic Multilingual Plane,BMP),即范围在U+00000000到U+0000FFFF的代码点,若将UCS-4 BMP前面的两个零字节去掉则可得到UCS-2(U+0000 ~ U+FFFF)。每个平面有216=65536个码位。Unicode计划使用了17个平面,一共有17×65536=1114112个码位。在Unicode 5.0.0版本中,已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中,平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000~0xFFFFD和0x100000~0x10FFFD。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为PUA。平面0也有一个专用区:0xE000~0xF8FF,有6400个码位。平面0的0xD800~0xDFFF共有2048个码位,是一个被称作代理区(Surrogate)的特殊区域。代理区的目的是用两个UTF-16字符表示BMP以外的字符(在讲UTF-16编码时会介绍)。

在Unicode 5.0.0版本中,238605-65534×2-6400-2408=99089,余下的99089个已定义码位分布在平面0、平面1、平面2和平面14上,对应Unicode目前定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、3419、43253和337个字符。平面2的43253个字符都是汉字。平面0上定义了27973个汉字。

再归纳总结一下:

① 在Unicode字符集中的某个字符对应的代码值称作代码点(Code Point),简称码点,用十六进制书写,并加上U+前缀。

② 后来字符越来越多,最初定义的16位(UCS-2版本)已经不够用,就用32位(UCS-4版本)表示某个字符的代码点,并且把所有代码点分成17个代码平面(Code Plane):其中,U+0000~U+FFFF划入基本多语言平面(Basic Multilingual Plane,BMP);其余划入16个辅助平面(Supplementary Plane),代码点范围为U+10000~U+10FFFF。

③ 并不是每个平面中的代码点都对应有字符,有些是保留的,有些是有特殊用途的。

3.7.2 VC-Qt开发环境对Unicode和多字节的支持

VC下Qt开发环境支持两种字符集:多字节字符集和Unicode字符集,默认采用Unicode字符集。我们可以创建项目验证一下。

【例3.6】 VC下验证默认情况的Qt项目字符集

(1)启动VC2017,新建一个Qt控制台项目,项目名为test。

(2)打开项目属性对话框,展开左边的“C/C++→预处理器”,在右边“预处理器定义”旁可以看到UNICODE和_UNICODE的宏定义,如图3-7所示。

图3-7

这就说明Qt项目默认采用的是Unicode字符集。如果喜欢多字节字符集,可以把UNICODE和_UNICODE删掉,参看下例。

【例3.7】 在VC下使用多字节字符集

(1)启动VC2017,新建一个Qt控制台项目,项目名为test。

(2)打开项目属性对话框,展开左边的“C/C++→预处理器”,在右边“预处理器定义”旁可以看到UNICODE和_UNICODE的宏定义,把UNICODE和_UNICODE删掉,然后单击“确定”按钮关闭项目属性对话框。

(3)打开test.cpp,输入如下代码:

因为我们删除了UNICODE和_UNICODE两个宏定义,所以这里的TCHAR相当于char,_tcslen相当于strlen,因此运行结果都是5。

(4)保存项目并运行,结果如图3-8所示。

图3-8

新建一个VC项目后,可以在项目属性里选择本项目所使用的字符集(左边选择“常规”,右边就能看到“字符集”选项了)。一定要记住:此选项只控制TCHAR、_T和通用形式的Win32 API函数是用宽字符版的还是多字节字符版的。如果选择了“使用Unicode字符集”,那么代码里用到的API函数就会被解释为UNICODE版本的API(带标记W的API),比如MessageBox被解释为MessageBoxW。如果选择了“使用多字节字符集”,则代码里用到的API函数被解释为多字节版本的API(带标记A的API),如MessageBox被解释为MessageBoxA。再比如对于代码中的宏_T,如果选择了Unicode字符集,则被解释成L,其后的字符串是双字节字符串;如果选择多字节字符集,则其后的字符串是单字节字符串。

如果项目中使用了“多字节字符集”(就是系统预定义了宏_MBCS),则类型TCHAR将映射到char。如果项目中使用了“Unicode字符集”(就是系统预定义了宏_UNICODE),则类型TCHAR将映射到wchar_t。在上例中,如果我们选择字符集选项为“Unicode字符集”,那么输出就会是5和4了。我们可以验证一下。

【例3.8】 通过字符集选项设置Unicode字符集

(1)打开VC2017,新建一个Qt控制台项目,项目名为test。

(2)打开项目属性对话框,展开左边窗格中的“C/C++→预处理器”,在右边窗格的“预处理器定义”旁可以看到UNICODE和_UNICODE的宏定义,把UNICODE和_UNICODE删掉,然后单击“确定”按钮关闭项目属性对话框。

(3)打开test.cpp,输入如下代码:

因为我们删除了UNICODE和_UNICODE两个宏定义,所以这里的TCHAR相当于char,_tcslen相当于strlen,应该都是5。

现在通过选中字符集的方式改回Unicode。打开项目属性对话框,在左边的窗格中选择“配置属性→常规”,在右边窗格的“字符集”旁边选择“使用Unicode字符集”,如图3-9所示。然后单击“确定”按钮,项目的字符集就切换到Unicode了,所以这里的TCHAR相当于wchar_t,_tcslen相当于wcslen,也就是5和4。

图3-9

(4)保存项目并运行,运行结果如图3-10所示。

图3-10

预处理中的UNICODE、_UNICODE与“常规”中的“字符集”选项,哪个优先级高?答案是前者高。如果不删除预处理中的UNICODE和_UNICODE,而把“常规”中的“字符集”选择为“使用多字节字符集”,那么最终运行结果依然是当成Unicode编码进行处理。

以上就是在VC下控制项目字符集的方式。在Qt中,建议采用支持Unicode字符集的开发软件,因为使用Unicode字符集开发热键好处颇多,比如:

· Unicode字符集使程序的国际化变得更容易。

· Unicode字符集提升了应用程序的效率,因为代码执行速度更快,占用内存更少。Windows内部的一切工作都是使用Unicode编码的字符和字符串来进行的。所以,假如你非要传入ANSI编码的字符或字符串,Windows就会被迫分配内存,并将ANSI字符或字符串转换为等价的Unicode编码的形式。

· 使用Unicode字符集,应用程序能轻松调用所有的Windows函数,因为一些Windows函数提供了处理Unicode字符和字符串的版本。

· 使用Unicode字符集,代码很容易与COM集成(后者要求使用Unicode编码的字符和字符串)。

· 使用Unicode字符集,代码很容易与.NET Framework集成(后者要求使用Unicode编码的字符和字符串)。

3.7.3 Qt Creator开发环境对Unicode和多字节的支持

在3.7.2小节我们验证了VC-Qt对Unicode和多字节两种字符集的支持情况,那么Qt Creator对Unicode和多字节又是如何支持或如何进行切换的呢?

下面几个例子将使用Qt Creator 4.8.2,但要注意两点:一是项目路径中不要有中文,二是项目路径要预先在磁盘上创建好。这也是笔者建议大家使用VC的原因之一,大公司的产品就是功能完善。

【例3.9】 在Qt Creator中验证默认情况下Qt项目的字符集

(1)启动Qt Creator 4.8.2,新建一个Qt控制台项目,项目名为test。

(2)在test.cpp中输入如下代码:

Qt Creator创建的项目默认采用的字符集是Unicode,所以上面的代码将输出aaabbb。

(3)保存项目并运行(按Ctrl+R快捷键),结果如图3-11所示。

图3-11

要在Qt Creator项目中使用多字节字符集,该怎么办呢?方法是在项目文件(.pro文件)中进行手工设置。请看下例。

【例3.10】 在Qt Creator中使用多字节字符集

(1)启动Qt Creator 4.8.2,新建一个Qt控制台项目,项目名为test,注意路径中不要含中文或空格。

(2)在test.cpp中输入如下代码:

(3)在Qt Creator的项目视图中,双击test.pro项目文件以打开它,在QT-=gui语句下面添加一行语句DEFINES-=UNICODE,即:

    QT -= gui
    DEFINES -= UNICODE

(4)保存项目并运行,运行结果如图3-12所示。

图3-12

通过这两个小例子就可以知道在Qt Creator中创建项目时默认采用的字符集是什么以及如何修改默认字符集了。

3.7.4 在Qt Creator中使用中文字符的两种方式

为了在Qt程序中正确显示中文而不出现乱码,通常有两种解决方案。

(1)使用静态函数QString::fromLocal8Bit

该函数将字节数组对象转为QString类对象,这个函数的原型声明如下:

    QString QString::fromLocal8Bit(const QByteArray &str)

在要显示中文的地方这样调用:

    QString::fromLocal8Bit ("信息1")

为了代码简洁,也可以把QString::fromLocal8Bit定义为一个宏:

    #define z QString::fromLocal8Bit

之后,在要显示中文的地方这样调用:

    z("信息2")
(2)在文件开头使用预处理命令execution_character_set
    #pragma execution_character_set("utf-8")  //用于正确显示中文

这样,在后面的代码里就可以直接使用中文了。