Zade's Weblog

程序人生

Monthly Archives: 2月 2010

linux+qt环境的中文问题

  • 字符集Charset

每个要处理的字符在计算机内部都要映射为一定的数字.那么我们要映射那些字符,如何映射呢?

由于计算机最早是美国发明的,他们最早制定了要映射那些字符,也就是我们常说的ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)字符.这个决定的影响是巨大的,它使得任何以后制定的字符集必须兼容ASCII.ASCII使用一个8位即可存储.

要制定一个字符集,要考虑的因素是存储节省和处理方便.

我们考虑GB2312,首先它必须兼容ASCII,这可以使用一个Byte存储,但是对于汉字,一个byte是不够的,至少需要两个byte.那么对于中英文混合的一段文字,例如:”中国ABC”,GB2312的存储结果是:0xD6D0,0xB9FA,0x41,0x42,0x43.如果我们认为每个汉字出现的几率是相等的,那么这个存储方案是最优的,也就是说,占用的存储量最少.

这个编码方案对存储介质来说是最好的,但是对于处理器而言则是一个灾难.一个很简单的需求, 字符串”中国ABC”的长度是多少?你可能很快的回答是5,但是对于计算机而言,却不是这样.它可以回答是7(GB2312编码),也可以是5(Unicode编码),甚至可以是10(Unicode编码,但是以字节为单位计算长度).无论如何,7(GB2312的答案)都是一个最坏的结果,因为它可能会让你处理半个汉字,也可能处理整个英文字符.究竟是那种处理结果,依赖的是字符串的具体内容;这简直让人抓狂.

在处理系统看来,GB2312,GBK等都是一个MBCS(Multibyte Char Set),这大大增加了字符串处理的难度;这里所谓的处理也包括对这些字符的显示.在处理系统内部,最好的方式是在把这些编码转换为统一长度的编码方案.这也是Unicode的初衷.

Unicode的作用是从处理优先的角度来看待字符编码;相对于GB2312等MBCS方案,这显然增加了存储的代价.存储和处理这两个相互制衡的因素是成对出现的.

  • 字符的显示

很显然,字符的显示是字符处理的第一步.

字符的显示涉及到那些因素呢?

    • 编码

例如我们知道”中国ABC”的GB2312编码方案是:0xD6D0,0xB9FA,0x41,0x42,0x43;UTF-16方案是: 0x4e2d, 0x56fd, 0x0041, 0x0042, 0x0043;UTF-8的方案0xE4,0xB8,0xAd,0xE5,0x9b,0xbd, 0x41,0x42,0x43.

对于要显示这些字符的系统而言,最好的结果是首先把这些不同存储的编码转换为一种统一的编码,这一般都是UTF-16,例如windows和Qt都是这样的.

对于Unicode以前的文本存储,都不支持不同的编码.也就是说,对于一个给定的存储结果,例如1.txt,要显示它,你必须假定它是以某种编码方案存储的.当然,这种假定肯定存在风险.那么,一般的处理系统会作这个假定呢?从操作系统的角度讲,都有一个默认的从非unicode到unicode的转换策略(参考windows的控制面板->区域和语言选项->高级),linux系列有一个LC_ALL(还包括LC_CTYPE等)的环境变量.所以一般的处理系统都会选择操作系统的这个策略,作为自己的假定.当然,你也可以实行自己的设定,例如浏览器都支持设定编码方案,还有linux下的GEdit等.

当这个假定不正确的时候,显示就不正常了.例如中文windows的假定都是GBK,CP是936;而中文linux的假定是utf-8,CP是65000.这样,当我们使用windows编辑的含有汉字的文本文件在linux系统使用Gedit打开时,乱码出现了(比较有意思的是,使用Gedit编码的文本文件,默认存储为utf-8的编码方案,windows的notepad能够自动的识别).

显然,我们必须使用更好的存储方案.

    • Unicode存储方案

Unicode使用BOM(Byte Order Marker)来存储unicode的编码方式.这样当应用程序处理文本的时候,首先根据存储在最前端的BOM决定文本的编码方式.BOM总是存储在最前端,在读取的时候,系统总是最先读取BOM,然后决定是使用双字节,单字节等来读取文本内容.

Unicode的方案非常好,但是最重要的问题是和原来的方案是不兼容的.所谓原来的方案,指得是使用MBCS存储方案(没有存储BOM).也就是说,我们需要一种使用MBCS存储(当然更支持Unicode存储),但是支持编码方式的方案.这就是XML.

    • XML存储方案

XML是一种标签语言,我们可以使用标签来设定当前文本的编码方案.所有的xml文档都使用下面类似的前置内容. <?xml version=’1.0′ encoding=’UTF-8′ standalone=’yes’?>.

XML的意义不是它也支持unicode存储方式,更重要的是它兼容原来的存储方案,支持文本的存储.

  • 字体

编码支持解决了系统内部的识别问题,但是如何把特定编码的字符显示在设备上,必须知道字符的字形信息.而这个是由字体文件决定的.

关于字体文件的详细信息具体另说.

  • 操作系统多中文的支持

对操作系统而言,中文只是一个特例;它要考虑的是如何从存储和处理的角度上支持所有的编码方案.

为了统一,操作系统必须在底层采用一致的编码方案;而很显然,这个方案就是Unicode. Windows在早期是MBCS,到windows2000以后,采用的是unicode-16;现在的linux大都采用utf-8的方案.

所以操作系统要处理中文,都要转换为其底层的编码方式.

  • C/C++对中文的支持

    • 本地设置

本地设置对C的转换函数影响很大,setlocale.

本地设置函数,支持两种常见的设置:

一种是传统的C式:setlocale(LC_ALL,“C”),这个设定保证了是ASCII的编码方案;

另一种是:setlocale(LC_ALL,””),这个设定保证了文本编码方案和操作系统的设置是一致的.

有时候这两种调用的结果对编码方案的影响是一样的,因为操作系统的设置也可能是MBCS的(当然,对其他的印象可能是不一样的,例如数字的输出格式)

    • 转换函数

mbslen

wcstombs

mbstowcs

这几个函数的真正含义我一直没有搞的很明白,我相信很多人也和我一样.以mbstowcs函数为例,我们可以简单的定义这个函数的功能为:按照当前的本地设置(LC_CTYPE),把MBCS字符串的文本转换为等价的宽字节字符串.

但是我们还需要详细的阐述这个定义.

首先宽字节字符串并不等于unicode.这是因为mbstowcs转换的结果和LC_CTYPE的设定是一致的,如果LC_CTYPE的设定为”C”,那么这个转换其实和没有转换是一样的,还是原来的老样子,当然也不可能是unicode.

另外,我们知道windows还有几个函数经常和这几个函数搞混, MultiByteToWideChar还有反向的WideCharToMultiByte,这两个函数是真正的把MBCS转换为UNICODE的函数.当LC_CTYPE的设定为””的时候,就会执行这种真正的向unicode的转换.

    • 源文件编码和文本常量

CPP的源文件的编码方式是很重要的,因为它影响文本常量的编码方式.

早期的CPP编码方式都是MBCS方式,或者叫ANSI的方式.现在很多人提倡linux下的utf-8方式.我个人还是支持MBCS方式,一者是和原来的方式兼容,另外windows和linux对待utf-8的方式并不一样(主要是是否支持BOM).

如果我们按照MBCS的方式,那么文本常量如何转换?或者我们以这个例子更好的说明问题:printf(“%sn”,”中国ABC”);这个语句是如何被编译器编译的.我们可以这样简单的描述:首先编译器把常量字符串”中国ABC”放置到一个常量区,那么以什么样的编码放置呢?它采用和源文件文本一样的编码方式,我们的源代码是MBCS编码的,严格来说这不是一种编码方式,正如我们前面所说,它要根据操作系统的设置来猜测具体的编码方式.在中文windows下是GBK,在中文linux下是utf-8.如果我们的源代码是在windows下书写的,那么汉字被编码为2个字节,在执行的时候,文本常量被取出来,再按照GBK转换为unicode,能够正确的展示.但是在linux下,汉字也被编码为两个字节,放置在常量区,在执行的时候, 文本常量被取出来,再按照utf-8转换为unicode,这就不能够正确的展示了(如果要想正确的展示,需要被编码为3个字节的unicode,而不是两个字节的GBK).

我们这么罗嗦的分析这么简单的代码的结论是:不要在源代码中嵌入汉字的常量字符串,这样不是跨平台的做法;替代的方式是存在文件当中.

    • 输入和输出

如果我们简单的问:在函数调用中printf(“%s”,text),text是含有中文的字符串,它能够正确的输出吗?

这没有答案,因为我们需要知道输入text的编码,如果这个编码和当前系统的设置是一致的,那么可以;否则不行.

  • Qt对中文的支持

Qt对中文的的支持也依赖于操作系统,但是它有自己独立的设置编码的方式.主要是通过QTextCodec的方式.

如果我们在网络上搜索”Qt中文显示”,会得到类似的如下答案:

QTextCodec::setCodecForTr(QTextCodec::codecForName("GBK"));

QTextCodec::setCodecForLocale(QTextCodec::codecForLocale( ));

QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale());

它或许能够让你的代码正常的工作,但是一定要搞清楚这些函数的含义;否则在以后的某个时刻,你会发现你的代码又不正常了(尤其是跨平台的时候).

首先,如果你进行设置,QTextCodec::codecForLocale()->name()一般是”System”,也就是系统的设置,这和我们在前面讨论的系统的默认设置时一致的.在大多数的情况下,我们是不需要更改这个设置的.这个对应于QString的fromLocale8Bit和toLocale8Bit.

另外QTextCodec::codecForCStrings(),这个对应于源文件中的字符串的解析,例如QString text(“中国”);我们说标准的string使用的是和源文件相同的编码,而QString则通过codecForCStrings来设定这个编码. 这个对应于QString的fromAscii和toAscii.

还有QTextCodec::codecForTr(),这个对应于字符串的国际化翻译.

如果让我设定的话,我不会更改QTextCodec的任何值.如果我们需要在源文件中提供常量输入字符串,那么QTextCodec::codecForCStrings会起作用,但是正如我们前面所说,不要这样做,这会带来平台移植的问题,而qt的目标不正是跨平台吗?

设定QTextCodec::codecForLocale(),更会引来更多的问题.主要的一个是如果我们从QfileDialog::getOpenFileName这样的函数得到的文件名字符串就是按照本地的设置得到的,而如果我们更改了QTextCodec::codecForLocale,那么我们要把这个结果转变为标准的string就比较麻烦了.简而言之,改变系统的环境变量是要竭力避免的.

所以我们正确的做法是,在适当的地方使用QTextCodec,而不是胡乱的全盘设定,这跟CPP中使用全局变量没有区别.

小妞妞好像要长牙了

昨天老婆说,小妞妞好像要长牙了。我很奇怪她是怎么知道的,老婆大人告诉我,小妞妞在吃奶的时候,开始咬人了,所以判断开始长牙了。
这里的逻辑我是不清楚的,但是老婆大人的话应该没有问题;我更加觉得奇怪的是,小孩长牙有这么早的吗?

共享指针和接口设计

原来我有过一段关于共享指针和接口设计的论述,现在又有了一些新的体会。
使用共享指针shared_ptr和基于接口的设计,可以取得和C# java等类似的结果,即基本的不同考虑内存的问题,当然在语法上可能会麻烦一些.但是这里面会有一些更深层次的问题.
为了说明这个问题,我们把接口对象分成大粒度对象和小粒度对象.对于小粒度对象而言,使用共享指针会有一些顾虑,比如不必要的内存占用.而且,在很多的情况下,对象本身有一种深层次的包含关系;也就是说,包含对象死了,被包含对象也不会存在.在这种情况下使用共享指针就没有必要了.但是大粒度对象可以使用共享指针.
共享指针也有很多的形式,shared_ptr,scoped_ptr,auto_ptr等.shared_ptr又很多的优点,但是没有release接口,这使得它在某些情况下不太方便.这也是scoped_ptr,auto_ptr优于它的地方.
在boost的ptr_container中,release的返回值使用了auto_ptr,这是一个非常绝妙的应用,在这里使用shared_ptr是不合适的.