您的位置:首页 > 编程语言 > C语言/C++

The New C++ -- 基本数据类型和字面值常量 (5. 宽字符类型和宽字符字面值常量)

2012-02-10 09:39 357 查看
注意:本章节内容设计C++11,其中部分内容可能还没有被所有编译器支持。

随着计算机技术的发展,软件国际化是不可避免的趋势。ASCII所支持的最多256种字符已经远远不能满足国际化的需求。对我们来说,仅已知的汉字就至少有9万多个!当然,要支持更多的字符就需要有更大的字符类型。C++定义了三种宽字符类型,char16_t,char32_t和wchar_t。

小知识:你也许好奇为什么这些命名和前面的int,char之类的不太一样,后面都跟着一个 _t,这其实是C语言的传统。C的程序员习惯把用户自定义的类型都加上 _t这个后缀,表示类型(type)。char16_t,char32_t和wchar_t 算是C语言标准规定的用户自定义类型,但他们只算``二等公民''。他们其实分别和某一个前面提到过的整型(``一等公民'')是同一种类型。C++中直接将这些类型提升为``一等公民'',他们在C++中和其他的整型属于不同的类型。但为了保持C的兼容,所以C++中也用
_t这个后缀。

这几种宽字符类型的区别可以看下表,

字符类型 占用的内存空间 所能表达的最少数据范围
char16_t>= 16位0到65535
char32_t>= 32位0到4294967295
wchar_t>= 16位-32767到32767,或者0到65535
从上表可以看出char16_t和char32_t分别最少有16位和32位,他们的值没有符号,属于无符号整形。同前几小节,可以用sizeof表达式得到编译器中他们的实际大小。char16_t是用来支持国际通用字符集(Universal Character Set,也叫Unicode)(注脚:本系列教程中将使用其更常见的名字Unicode。Unicode和国际通用字符集是不同的标准,但是他们支持相同的字符到整数的映射,本节把他们当成同一个标准。)的UCS-2/UTF-16编码,char32_t是用来支持Unicode的UCS-4/UTF-32编码。和char一样,C++标准并没有规定wchar_t是有符号的还是无符号的,wchar_t至少有16位,他是用来支持执行宽字符集(execution
wide character set)的。不严格的说,C++中的执行宽字符集所支持的字符需要囊括源代码中可能用到的所有字符。wchar_t和char16_t不同的是,执行宽字符集(即wchar_t)可以是Unicode,但也可以不是,是编译器自定义的行为。

小知识:Unicode又称国际通用字符集,是为了统一的表达世界上所有的语言的字符而诞生的。目前Unicode包含10多万种字符并且还在不断增加中。Unicode将每一种字符都映射到一个不同的整数上,这个整数就叫做码点(code point)。注意这个映射是唯一的,例如国家的国就被映射为0x56FD这个整数,而国的繁体字(國)则被映射为0x570B。Unicode虽然定义了所有字符的码点,但是并没有规定每个码点应该以如何的形式存储以及传输。UTF-8,UTF-16以及UTF-32正是最常见的Unicode的存储和传输方法。

也许你要问为什么需要多种不同的存储和传输方法呢?这是因为不同的方法有各自的优点和缺点。如果要涵盖Unicode的所有码点,则需要32位的整数,但这在绝大多数情况下是极其浪费的。ASCII只需要8位整数就可以满足大部分英文的需求,而如果用32位整数来代替原来的8位整数的话,则所有英文将占用4倍于以前的内存和4倍的网络带宽。UTF-8在这种情况下是很好的选择。UTF-8并不是固定长度的,当码点的整数值在0x00到0x7F之间时,UTF-8只需要一个字节8位来表示这个码点,这部分是和ASCII兼容的。但当code
point的整数值在0x80到0x7FF之间时,UTF-8需要2个字节16位来表示。当码点在0x800到0xFFFF之间时,UTF-8需要3个字节24位来表示。UTF-8所支持的最大码点是0x10FFFF,需要4个字节32位来表示。虽然Unicode的最大码点是0xFFFFFFFF,但现在已知的所有字符都在0x10FFFF之内。所以UTF-8是可以表达现在已定义的所有字符的。

从英文角度来讲,UTF-8是一种很好的选择,但是UTF-8对汉语却不是很友好。这是因为所有的汉字用UTF-8都必须用至少3个字节24位来表示,即使几乎所有的常用的汉字的码点都小于0xFFFF(只需16位)。UTF-16是汉语的一个不错的选择。UTF-16对于0x0000到0xD7FF和0xE000到0xFFFF之间的码点是可以直接表达的,就是说码点值即是UTF-16的编码值。注意Unicode并没有映射任何字符到0xD800到0xDFFF之间,所以不需要编码。UTF-16用4个字节来表达0x10000到0x10FFFF之间的码点,前两个字节在0xD800到0xDBFF之间,后两个字节在0xDC00到0xDFFF之间。用UTF-16,常用的汉字就只需要2个字节就可以了。

不难推测,UTF-32的编码和Unicode的码点是一一对应的。但是因为很浪费空间,实际应用中还是以UTF-8和UTF-16为主。

宽字符字面值常量和字符字面值常量类似,都由单引号将字符引起来。为了区分,宽字符字面值常量在最前面要加上一个前缀。前缀u,U和L分别表示char16_t,char32_t和wchar_t。例如'a',u'a',U'a'和L'a'分别是char,char16_t,char32_t和wchar_t类型的字面值常量。和char类型的字符字面值常量一样,单引号里面可以用八进制转义字符和十六进制转义字符。前面说过,八进制转义字符是由反斜线 \ 加上最多三个八进制数字组成的,他支持的最大数只有(777)_8(下标表示八进制)。所以我们一般不用八进制转义字符表达char16_t和char32_t类型的字面值常量,而用十六进制转义字符。十六进制转义字符里面的十六进制数字的数目是不限的,但你要保证他不超过相应字符类型所支持的最大数。例如国家的国用char16_t类型的字面值常量表示就是u'\x56FD',而用char32_t类型的字面值常量表示就是U'\x56FD'。

对于宽字符字面值常量,C++中还可以用两个特殊的转义字符 \u 和 \U来表示。这两个转义字符是专门为支持Unicode所准备的。\u后面必须跟四个十六进制数字(不足四位前面用零补齐),表示Unicode中在0xFFFF之内的码点(但不能表示0xD800到0xDFFF之内的码点)。\U后面必须跟八个十六进制数字,表示Unicode中所有可能的码点(除0xD800到0xDFFF之外)。对于char16_t和char32_t这两个专门支持Unicode的宽字符类型来讲,用 \u 和 \U 比起 \x 更自然一些。以国家的国为例,char16_t的字面值常量可以表示成u'\u56FD'或者u'\U000056FD',char32_t的字面值常量可以表示成U'\u56FD'或者U'\U000056FD'。注意前缀和反斜线后面的u和U并不需要配对,但是对于char16_t来讲,用\U时要注意不能超过0xFFFF,否则编译器会报错。

对于wchar_t类型的字面值常量来说,也可以用八进制,十六进制转义字符或者\u和\U来表示。但是要注意里面的数字不要超过wchar_t的最大范围(不同的系统或者编译器不一样)。wchar_t特殊的地方在于他支持的是执行宽字符集,并不一定是采用Unicode的编码,所以即使你用了\u或者\U这两种Unicode的转义字符,编译器也会负责自动将里面Unicode转成执行宽字符集的编码。但是在现在大部分流行的编译器中,例如Visual C++和GCC,wchar_t缺省的情况下都采用Unicode的编码。在Windows下的Visual
C++中,wchar_t采用的是UTF-16编码,一个wchar_t是16位。所以在Windows中,wchar_t的字面值常量不能超过0xFFFF,否则编译器会报错。但在Linux下的GCC中,缺省采用的是UTF-32编码,一个wchar_t是32位,可以支持所有可能的Unicode编码。

无论你的倾向于用普通的\x还是专门支持Unicode的\u或者\U,除非必要,最好不要一会用\x,一会用\u或者\U,以免造成软件维护的困扰。

小知识:我们提及了几种表示宽字符字面值常量的方法,很多人也许已经开始疑惑,为什么我们要写\textbackslash x,\textbackslash u,\textbackslash U等来表示宽字符呢,我们能不能直接用汉字表示呢?例如u'国',U'国'和L'国'。答案是肯定的,但是要取决于编译器的支持。一些主流的编译器如Visual C++和GCC都是支持的,编译器会自动把里面的汉字转换成Unicode的编码。

练习:宽字符字面值常量wchar_t也可以文本输出,但输出前要定义正确的编码方式。以Windows为例,虽然wchar_t采用Unicode编码,Windows输出时采用的编码并不是Unicode,而是你在Windows设置中设置的本地化的语言编码。如果是中文,就是GB18030编码(GB18030兼容GB2312和GBK,这些编码和Unicode编码不同,即同一个汉字所映射的整数值不同),程序会自动将Unicode转换成本地化的语言编码。输入下面的程序,查看运行结果。

#include <iostream>
#include <locale>
int main()
{
//*@\color{red!70!green!70!blue!70}下面这行将输出设置成按照用户自己设置的本地化编码输出@*
std::wcout.imbue(std::locale(""));
//*@\color{red!70!green!70!blue!70}注意我们没有用cout,针对wchar_t宽字符类型的输出,我们用wcout@*
std::wcout << L'\x56FD' << L'\u56FD' << L'\U000056FD' << L'国' << std::endl;
return 0;
}


要注意的是,char16_t和char32_t并不能像wchar_t一样直接用wcout作文本输出,他们是用来支持Unicode的各种编码,解码以及文档读取和存储等操作的,并不适合作文本输出(但他们可以用二进制形式输出,我们会以后提及)。宽字符的文本形式输出是和编译器内部的执行字符编码以及用户设置的本地化编码方式紧密相关的,所以用wchar_t更自然一些,因为wchar_t本身就是实现自定义的行为,不同的编译器可以用不同的执行字符编码以及采用不同的执行字符编码到用户设置的本地化编码的转换。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐