4.11 国际化

现在,软件工业是一个新兴的、健康的、具有世界范围的经济市场,这需要应用程序能在多种语言环境下运行。早在20世纪80年代,C标准委员会就加入了对非美国表达方式习惯的区域性(locale)机制的支持。所谓区域性是在显示一些实体,如时间和货币数量时当地人们习惯使用的方式。在20世纪90年代,C标准委员会同意补充处理宽字符(wide character)(由类型wchar_t表示)的特殊函数进入标准C,容许这些函数支持非ASCII码字符集,一般用于西欧诸国范围。尽管宽字符所占空间的大小并不特殊,但在一些操作平台上把它按32位字长进行实现,可以满足统一代码协会(Unicode Consortium)对编码的特殊需要,同时也适用于亚洲标准化组织定义的多字节字符集。C++支持宽字符和区域(locale)字符,把两者整合到了输入输出流类库中。

4.11.1 宽字符流

宽字符流(wide stream)是一个处理那些宽字符的流类。目前引入的所有例子(除了第3章中那些带有宽字符特征的例子外)都使用专门处理char类型的窄(narrow)字符流。因为流操作的本质都是一样的,与基础字符类型没有关系,所以一般将其封装成模板。例如,可以将所有输入流类都与类模板basic_istream连接来定义:

4.11 国际化 - 图1

事实上,根据下面的类型定义,所有输入流类都是该模板的特化:

4.11 国际化 - 图2

其他流类型的定义与此类似。

总而言之,读者用这些方法可以创建不同字符类型的流。但事情也不是那么简单。原因是提供给char类型和wchar_t类型的字符处理函数的名称不相同。比较两个窄字符串,比如使用函数strcmp()。而用于两个宽字符的比较函数为wcscmp()。(请记住这些函数在C语言中的原始声明,这些函数没有重载版本,所有的函数名需要具有惟一性。)正因为如此,一般情况下流类对象的比较运算符不能仅仅调用strcmp()。这就需要引入一种方法,使用这种方法可以在进行流对象的比较操作时自动调用正确的底层函数。

解决的办法是找出它们因子的差异成为一个新的抽象。对字符的操作被抽象成为一个char_traits模板,正如在第3章结尾处讨论的,这个模板中预定义了char(字符型)和wchar_t(宽字符型)类型。比较两个字符串时,basic_string先调用traits:compare()(记住特征参数traits是模板的第2个参数),traits:compare()再根据所用的字符类型调用strcmp()或wcscmp()。(对于basic_string来说,这一点是必须清晰的。)

如果访问底层字符处理函数,只需要关注char_traits,但大多数情况下不需要特别注意。然而,为了增强程序的健壮性,需要将插入符和提取符定义为模板,以适应用户要在宽字符流上对它们的使用。

为解释清楚,回忆一下本章开始时引入的类Date中的插入符。它的原始定义如下:

4.11 国际化 - 图3

这个插入符只能用于窄字符流。为了使其具有通用性,现在把它定义成基于basic_ostream的模板:

4.11 国际化 - 图4

4.11 国际化 - 图5

注意,也需要用模板参数charT替换char来声明fillc,因为fillc的声明依赖于模板实例化时的参数是char还是wchar_t。

因为在定义模板时不知道所使用的流的类型,所以需要有一种自动将字符文字的长度转换成对于该流来说大小合适的方法。成员函数widen()负责处理这项工作。例如,对表达式widen('-')来说就是将其参数转变成L'-'(文字语法相当于wchar_t('-')转变),如果为宽字符流则不进行转换。反之亦然。如果需要,函数narrow()将字符转换成char类型。

可以使用widen()为本章前面较早提供的程序例子编写一个名为nl操纵算子的通用版本:

4.11 国际化 - 图6

4.11.2 区域性字符流

不同国家的计算机输出之间最显著的不同,在于分割整数和实数的小数部分所使用的标点符号。在美国,一个句号表示一个小数点,但是世界上大多数国家用逗号表示小数点。如果为各个区域的国家的输出显示分别定义不同的格式,这是十分不方便的。这里再一次使用抽象来解决这个问题。

这次的抽象是区域。每一流类都有相联系的区域对象,这些对象用来指出如何显示不同的文化背景下的确定的数量。一个区域对象管理一系列视文化不同而定的数量的显示规则,定义如下:

4.11 国际化 - 图7

下面的程序说明了基本区域性字符流的行为:

4.11 国际化 - 图8

4.11 国际化 - 图9

输出结果如下:

4.11 国际化 - 图10

默认的区域为“C”区域,是C和C++程序员多年来一直使用的(基本上是英语和美语文化)。所有的流最初都完全“浸透(imbue)”在“C”区域环境下。成员函数imbue()改变了一个流使用的区域。注意程序输出了“法语”区域在ISO(国际标准化组织)中的全称(即在法国表达的“法语”相对于其他国家表达的“法语”)。这个例子说明在这种区域下,数字中的小数点用逗号表示。如果想在这种区域的规则下进行输入,则需要把cin改变到相同的区域下工作。

每个区域目录被分成几个领域,每个领域都是一些对应于相应目录封装了特定功能的类。例如,目录time包含的领域有time put和time_get,它们分别含有进行时间、日期的输入(input)和输出(output)的函数。而目录monetary包含的领域有money_get、money put和moneypunct。(moneypunct决定了流通中的货币符号。)下面的程序说明了moneypunct领域。(time领域需要用到一种复杂的迭代器,它超出了本章的讨论范围。)

4.11 国际化 - 图11

输出了法国流通货币符号和小数点分隔符:

4.11 国际化 - 图12

读者也可定义自己的领域以构建个人化的区域。[1]但要当心,区域的开销是相当可观的。事实上,一些供销商提供了区别于标准C++库的不同风格的库,以便满足对使用标准库有限制条件的环境。[2]

[1]详情请参看Langer&Kreft的著作。

[2]例如,参看Dinkumware的Abridged库的网址http://www.dinkumware.com。这个库不支持locale,对异常的支持为可选项。