POCO C++库学习和分析 -- 字符编码

POCO C++库学习和分析 -- 字符编码


1. 字符编码

1.1 字符编码的概念

        字符编码可以理解为在计算机上语言符号和二比特数之间的映射。不同的编码方式对应着不同映射方法,对于映射集的双方而言,用一种映射方法下,映射关系是一一对应的。由于语言的基本符号是有限的,所以作为映射的双方,映射集也是有限的。下面这段概念的介绍来自于文章《字符编码:Unicode/UTF-8/UTF-16/UCS/Endian/BMP/BOM》、《C++字符串完全指引(ZT)》、《字符编码笔记:ASCII,Unicode和UTF-8》,并混杂了一些自己的理解。


1. ASCII码 

        对于英文字母而言,语言的符号是26个字母,因此在上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。 

2. 非ASCII编码 

        英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。 
        但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。 
        至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256×256=65536个符号。 中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。 


3. 各自定义非ASCII编码的问题

        由于各国都制定了自己的兼容ascii编码规范,就是各种ANSI码,比如我国的gb2312,用两个扩展ascii字符来表示一个中文。这带来了一个新问题,这些ansi码无法同时存在,因为它们的定义互相重叠,要自由使用不同语言就必须有一个新编码,为各种文字统一分配编码。 


4. 微软的解决方案

        微软为了解决这一问题,提出了一个自己的解决方案。windows上的MBCS方法,在 MBCS 下,字符被编码为单字节或双字节。在双字节字符中,第一个字节(即前导字节)表示它和下一个字节将被解释为一个字符。第一个字节来自留作前导字节的代码范围。哪个范围的字节可以用作前导字节取决于所使用的代码页。例如,日文代码页 932 使用 0x81 到 0x9F 范围内的字节作为前导字节,而朝鲜语代码页 949 则使用其他范围的字节。


5. 另一种解决方案Unicode

        虽然微软提了自己的方案,其他人也没闲着。为了解决这一问题。国际标准化组织(ISO)想出了一个办法,这个办法其实和微软也类似。即存在有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。 
        Unicode是一种字符编码方法,它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是”Universal Multiple-Octet Coded Character Set
”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。 

        Unicode现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。 
        根据维基百科的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。 在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。 目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。 


6. UCS-2、UCS-4、BMP 

        UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏: 
        UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。 
        UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。 
        group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。 
        将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
        由于即使是老UCS-2,也可以表示2^16=65535个字符,基本上可以容纳所有常用各国字符,所以目前各国基本都使用UCS-2。  

7. Unicode的问题 

        值得注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。 
        比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。 
        这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。 
        它们造成的结果是:1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。2)unicode在很长一段时间内无法推广,直到互联网的出现。 
        怎样存储和传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16、UTF-32。

8. UTF编码

        UTF(UCS Transformation Format)规范设计时考虑了一些现实问题。即在UCS定义之前,已经存在大量的ASCII程序。新定义的UCS的表示方法必须兼容原始的ascii程序和方法。
        这个问题也可以表示为,Unicode使用2个字节表示一个字符,ascii使用1个字节,在很多方面产生了冲突,以前处理ascii的方法都必须重写。而且C语言用\0作为字符串结束标志,但Unicode中很多字符都含\0,C语言的字符串函数也无法正常处理Unicode。为了把unicode投入实用,出现了UTF,最常见的是UTF-8、UTF-16和UTF-32。

        其中UTF-16和Unicode本身的编码是一致的,UTF-32和UCS-4也是相同的,但最重要的是UTF-8编码方式。(UTF-32中字符的数量为2^32,也就是说用一个4 byte的int值可以表示一个人类字符。一个int值既然可以可以表示所有UCS-4中的字符,当然也可以表示UCS-2中对应的所有字符)。那为什么会出现UTF-8编码方式呢。UTF8是一种变长的编码,它的字节数是不固定的,使用第一个字节确定字节数。第一个字节首为0即一个字节,110即2字节,1110即3字节,字符后续字节都用10开始,这样不会混淆且单字节英文字符可仍用ASCII编码。理论上UTF-8最大可以用6字节表示一个字符,但Unicode目前没有用大于0xffff的字符,实际UTF-8最多使用了3个字节。 

        UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下: 

UCS-2编码(16进制)   
bit数
UTF-8 字节流(二进制)   
byte数
备注
0000 0000 ~
0000 007F
0~7
0XXX XXXX
1
0000 0080 ~
0000 07FF
8~11
110X XXXX
10XX XXXX
2
0000 0800 ~
0000 FFFF
12~16
1110 XXXX
10XX XXXX
10XX XXXX
3
基本定义范围:0~FFFF
0001 0000 ~
001F FFFF
17~21
1111 0XXX
10XX XXXX
10XX XXXX
10XX XXXX
4
Unicode6.1定义范围:0~10 FFFF
0020 0000 ~
03FF FFFF
22~26
1111 10XX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
5
0400 0000 ~
7FFF FFFF
27~31
1111 110X
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
10XX XXXX
6

表一,UCS-2到UTF-8的编码方式表


        例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:011011000100 1001, 用这个比特流依次代替模板中的x,得到:111001101011000110001001,即E6 B1 89。 
        读者可以用记事本测试一下我们的编码是否正确。 
        UTF-16以16位为单元对UCS进行编码。对于小于0×10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0×10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0×10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。 

        看到这里,读者要问了,对于汉字来说,使用UTF-8来说,存储的字节数"E6 B1 89"要比直接使用Unicode编码"6C49"还多啊。没办法,对于汉字来说,确实增多了。但对于英语系国家来说,UTF-8比Unicode省了。谁叫计算机是别们发明的呢,总是有点特权的。


9 UTF8编码

         因为UTF8在互联网上应用非常广泛,所以对UTF8多唠叨两句。首先我们应该明确一个概念,UTF-8是Unicode的一种实现方式,即存储方式

         UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~6个字节表示一个符号,根据不同的符号而变化字节长度。

         UTF-8的编码规则很简单,只有二条

         1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

         2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

         因此可以用如何判断一个字符串是否是UTF8编码 中叙述方法去判断一个字符串是否为UTF8编码。

         UTF8 主要用在网络传输和文本处理上,在程序的内部最好不要使用UTF8格式编码,而直接使用UTF32。这样定义结构,会是程序逻辑更加清晰一些。原因基于以下考虑:

         在window内部,UTF8 可以被string保存,GB2312也可以被string保存,如果程序有多个入口,输入了不同的编码方式[UTF8数据来自网络,GB2312来自本地磁盘用string读入],在内部用string处理,逻辑上区分是个大麻烦。不如在入口处统统把他们转换成为UTF32(即wstring)。


10. Little endian和Big endian

        上一节已经提到,Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。 
        这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。 因此,第一个字节在前,就是”大头方式“(Big endian),第二个字节在前就是”小头方式“(Little endian)。 那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码? 
        Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。 

        如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。 


11. BOM和UTF8

       UCS规范建议我们在传输字节流前,先传输 字符"ZERO WIDTH NO-BREAK SPACE"。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little- Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM

       BOM(byte order mark)是为 UTF-16 和 UTF-32 准备的,用于标记字节序(byte order)。UTF-8 不需要 BOM,尽管 Unicode 标准允许在 UTF-8 中使用 BOM。

        UTF-8中使用BOM不是用来表明字节顺序的,而是用来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF,所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。这也是微软的做法。微软正是用这种方法(在 UTF-8 中使用 BOM), 把 UTF-8 和 ASCII 等编码明确区分开来,但这样的文件在 Windows 之外的操作系统里会带来问题。Unix社区尤其反对。

       所以不含 BOM 的 UTF-8 才是标准形式,在 UTF-8 文件中放置 BOM 主要是微软的习惯。


2. Poco中字符编码

2.1 编码的介绍

        有了上面的基本概念,对于Poco中的字符编码,理解就简单了。在Poco中存在ASCII,Latin1,Latin9,Windows1252,UTF16,UTF8编码。其中ASCII对应的字符集大小为128;Latin1和Latin9表达的对象为拉丁语,其对应的字符集大小为256;Windows1252对应的字符集大小也为256;UTF16,UTF8表达的字符集对象为UCS2,大小为2^32。下面把涉及的这几种编码说的详细一点:

       Latin1:Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。

  ISO-8859-1
  ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
  ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。
  因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。
       Latin9:

       看代码的话,和Latin1的区别在于,Latin1用2个字节去表示文字符号,而Latin9用4个字节表示文字符号。

       UTF8:

       UTF-8是UNICODE的一种变长字符编码又称万国码,由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码UNICODE字符。用在网页上可以同一页面显示中文简体繁体及其它语言(如日文,韩文)。UTF-8编码的优点,UTF-8编码可以通过屏蔽位和移位操作快速读写。字符串比较时strcmp()和wcscmp()的返回结果相同,因此使排序变得更加容易。字节FF和FE在UTF-8编码中永远不会出现,因此他们可以用来表明UTF-16或UTF-32文本(见BOM) UTF-8 是字节顺序无关的。它的字节顺序在所有系统中都是一样的,因此它实际上并不需要BOM。UTF-8编码的缺点,无法从UNICODE字符数判断出UTF-8文本的字节数,因为UTF-8是一种变长编码它需要用2个字节编码那些用扩展ASCII字符集只需1个字节的字符 ISO Latin-1 是UNICODE的子集,但不是UTF-8的子集 8位字符的UTF-8编码会被email网关过滤,因为internet信息最初设计为7位ASCII码。因此产生了UTF-7编码。 UTF-8 在它的表示中使用值100xxxxx的几率超过50%, 而现存的实现如ISO 2022, 4873, 6429, 和8859系统,会把它错认为是C1 控制码。因此产生了UTF-7.5编码。

       UTF16:

       UTF-16是Unicode的其中一个使用方式。 UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思。它定义于ISO/IEC 10646-1的附录Q,而RFC2781也定义了相似的做法。在Unicode基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个2字节的值来储存。UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节) 储存,但UTF-16却无法兼容于ASCII编码。c#中默认的就是UTF-16,所以在处理c#字符串的时候只能是byte,stream等方式去处理。

       UTF-32:
       UTF-32 (或 UCS-4)是一种将Unicode字符编码的协定,对每一个Unicode码位使用恰好32位元。其它的Unicode transformation formats则使用不定长度编码。因为UTF-32对每个字符都使用4字节,就空间而言,是非常没有效率的。特别地,非基本多文种平面的字符在大部分文件中通常很罕见,以致于它们通常被认为不存在占用空间大小的讨论,使得UTF-32通常会是其它编码的二到四倍。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。与UTF-8及UTF-16相比,它有点更容易遭截断。
       

2.2 字符原集和表示之间的转换类

       Poco中的编码类都从TextEncoding类继承。TextEncoding类的接口定义如下:

class Foundation_API TextEncoding
{
public:
	typedef SharedPtr<TextEncoding> Ptr;
	
	enum
	{
		MAX_SEQUENCE_LENGTH = 6 /// The maximum character byte sequence length supported.
	};
	
	typedef int CharacterMap[256];


	virtual ~TextEncoding();

	virtual const char* canonicalName() const = 0;

	virtual bool isA(const std::string& encodingName) const = 0;
			
               // ........
		
	virtual int convert(const unsigned char* bytes) const;

	virtual	int queryConvert(const unsigned char* bytes, int length) const;

	virtual int sequenceLength(const unsigned char* bytes, int length) const;

	virtual int convert(int ch, unsigned char* bytes, int length) const;


               // ....
		
protected:
	static TextEncodingManager& manager();
		/// Returns the TextEncodingManager.
};

       我们可以把要表示的字符集称为字符原集,如UCS2,UCS4,它规定了字符集中存在哪些字符,并把每一个字符和数字之间建立一一映射关系。由于字符原集是个有穷集合,一个int值(2^32)足以表示其定义。一个字符的原集在被应用到计算机中时,会存在多种表示方式,如UCS2可以表示为UTF8,UTF16,我们称为原集的表示。

       TextEncoding中下面两个函数,用来把”原集的表示“转换为原集字符(一个int值)。

       int convert(const unsigned char* bytes) const;
       int queryConvert(const unsigned char* bytes, int length) const;
       而下面这个函数则用来把原集字符(一个int值)转换成”原集的表示“。

	int convert(int ch, unsigned char* bytes, int length) const;
      拿UTF8Encoding类来举例,其原集为UCS2,表示方法是UTF8。

      ”原集字符“转成”UTF8“表示,其函数实现如下:

int UTF8Encoding::convert(int ch, unsigned char* bytes, int length) const
{
	if (ch <= 0x7F)
	{
		if (bytes && length >= 1)
			*bytes = (unsigned char) ch;
		return 1;
	}
	else if (ch <= 0x7FF)
	{
		if (bytes && length >= 2)
		{
			*bytes++ = (unsigned char) (((ch >> 6) & 0x1F) | 0xC0);
			*bytes   = (unsigned char) ((ch & 0x3F) | 0x80);
		}
		return 2;
	}
	else if (ch <= 0xFFFF)
	{
		if (bytes && length >= 3)
		{
			*bytes++ = (unsigned char) (((ch >> 12) & 0x0F) | 0xE0);
			*bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80);
			*bytes   = (unsigned char) ((ch & 0x3F) | 0x80);
		}
		return 3;
	}
	else if (ch <= 0x10FFFF)
	{
		if (bytes && length >= 4)
		{
			*bytes++ = (unsigned char) (((ch >> 18) & 0x07) | 0xF0);
			*bytes++ = (unsigned char) (((ch >> 12) & 0x3F) | 0x80);
			*bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80);
			*bytes   = (unsigned char) ((ch & 0x3F) | 0x80);
		}
		return 4;
	}
	else return 0;
}

     ”UTF8“表示转成”原集字符“,其函数实现如下:

int UTF8Encoding::convert(const unsigned char* bytes) const
{
	int n = _charMap[*bytes];
	int uc;
	
	switch (n)
	{
	case -6:
	case -5:
	case -1:
		return -1;
	case -4: 
	case -3: 
	case -2:
		if (!isLegal(bytes, -n)) return -1;
		uc = *bytes & ((0x07 << (n + 4)) | 0x03);
		break;
	default:
		return n;
	}

	while (n++ < -1) 
	{	
		uc <<= 6;
		uc |= (*++bytes & 0x3F);
	}
	return uc;
}

     这两段代码就是上面的表一《UCS-2到UTF-8的编码方式表》的代码表现。其他的编码也类似。在UTF16Encoding类中,由于UCS2和UTF16表示是一致的,所以不存在转换关系,但有big endian和litter endian实现问题。在ASCIIEncoding类中,原集和其表现也一致,所以也不存在转换问题。

     下面是  TextEncoding和其相关类的类图:


     TextEncodingManager是TextEncoding类的工厂类,创建了ASCIIEncoding、UTF16Encoding等编码对象。

2.3 字符集之间的转换

     不同字符集之间的转换,实际上是不同字符原集的不同表示之间的转换。如果两个表示方法的原集相同,转换起来自然方便一些。Poco中提供了UnicodeConverter类用于UTF8和UTF16之间的转换。其定义如下:

class Foundation_API UnicodeConverter
{
public:
	static void toUTF16(const std::string& utf8String, std::wstring& utf16String);
		/// Converts the given UTF-8 encoded string into an UTF-16 encoded wstring.

	static void toUTF16(const char* utf8String, int length, std::wstring& utf16String);	
		/// Converts the given UTF-8 encoded character sequence into an UTF-16 encoded string.

	static void toUTF16(const char* utf8String, std::wstring& utf16String);	
		/// Converts the given zero-terminated UTF-8 encoded character sequence into an UTF-16 encoded wstring.

	static void toUTF8(const std::wstring& utf16String, std::string& utf8String);
		/// Converts the given UTF-16 encoded wstring into an UTF-8 encoded string.

	static void toUTF8(const wchar_t* utf16String, int length, std::string& utf8String);
		/// Converts the given zero-terminated UTF-16 encoded wide character sequence into an UTF-8 encoded wstring.

	static void toUTF8(const wchar_t* utf16String, std::string& utf8String);
		/// Converts the given UTF-16 encoded zero terminated character sequence into an UTF-8 encoded string.
};
     注意UTF-16用在C++中是用wtring存储的。虽然UTF-16对应着UCS2,内部存储时,一个short已经足够。但在Linux下默认是占4个字节,当然在用GCC编译时可以使用-fshort-wchar来强制使用2个字节,而在Windows上被定义为unsigned short。

     如果两个表示方法的原集不同,则要考虑转换方向问题。比如说中文字符在ASCII码中不存在,那么毫无疑问,把中文字符转换成ASCII码自然无意义,这个方向的转换注定要失败。在Poco中,上述字符集之间的转换是用类TextConverter来实现的。下面是它的定义:

class Foundation_API TextConverter
	/// A TextConverter converts strings from one encoding
	/// into another.
{
public:
	typedef int (*Transform)(int);
		/// Transform function for convert.
		
	TextConverter(const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');
		/// Creates the TextConverter. The encoding objects must not be deleted while the
		/// TextConverter is in use.

	~TextConverter();
		/// Destroys the TextConverter.
		
	int convert(const std::string& source, std::string& destination, Transform trans);
		/// Converts the source string from inEncoding to outEncoding
		/// and appends the result to destination. Every character is
		/// passed to the transform function.
		/// If a character cannot be represented in outEncoding, defaultChar
		/// is used instead.
		/// Returns the number of encoding errors (invalid byte sequences
		/// in source).

	int convert(const void* source, int length, std::string& destination, Transform trans);
		/// Converts the source buffer from inEncoding to outEncoding
		/// and appends the result to destination. Every character is
		/// passed to the transform function.
		/// If a character cannot be represented in outEncoding, defaultChar
		/// is used instead.
		/// Returns the number of encoding errors (invalid byte sequences
		/// in source).

	int convert(const std::string& source, std::string& destination);
		/// Converts the source string from inEncoding to outEncoding
		/// and appends the result to destination.
		/// If a character cannot be represented in outEncoding, defaultChar
		/// is used instead.
		/// Returns the number of encoding errors (invalid byte sequences
		/// in source).

	int convert(const void* source, int length, std::string& destination);
		/// Converts the source buffer from inEncoding to outEncoding
		/// and appends the result to destination.
		/// If a character cannot be represented in outEncoding, defaultChar
		/// is used instead.
		/// Returns the number of encoding errors (invalid byte sequences
		/// in source).

private:
	TextConverter();
	TextConverter(const TextConverter&);
	TextConverter& operator = (const TextConverter&);

	const TextEncoding& _inEncoding;
	const TextEncoding& _outEncoding;
	int                 _defaultChar;
};


       如果要在流输出之前,进行字符集转换,Poco还提供了类StreamConverterBuf。其定义为:

class Foundation_API StreamConverterBuf: public UnbufferedStreamBuf
	/// A StreamConverter converts streams from one encoding (inEncoding)
	/// into another (outEncoding).
	/// If a character cannot be represented in outEncoding, defaultChar
	/// is used instead.
	/// If a byte sequence is not valid in inEncoding, defaultChar is used
	/// instead and the encoding error count is incremented.
{
public:
	StreamConverterBuf(std::istream& istr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');
		/// Creates the StreamConverterBuf and connects it
		/// to the given input stream.

	StreamConverterBuf(std::ostream& ostr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');
		/// Creates the StreamConverterBuf and connects it
		/// to the given output stream.

	~StreamConverterBuf();
		/// Destroys the StreamConverterBuf.

	int errors() const;
		/// Returns the number of encoding errors encountered.

protected:
	int readFromDevice();
	int writeToDevice(char c);

private:
	std::istream*       _pIstr;
	std::ostream*       _pOstr;
	const TextEncoding& _inEncoding;
	const TextEncoding& _outEncoding;
	int                 _defaultChar;
	unsigned char       _buffer[TextEncoding::MAX_SEQUENCE_LENGTH];
	int                 _sequenceLength;
	int                 _pos;
	int                 _errors;
};


2.4 迭代子

     TextBufferIterator和TextIterator实现了对流和字符串进行迭代。其使用大致如下:

   UTF8Encoding utf8Encoding;
   char buffer[] = "...";
   TextBufferIterator it(buffer, utf8Encoding);
   TextBufferIterator end(it.end());
   int n = 0;
   while (it != end) { ++n; ++it; }

      或:

   UTF8Encoding utf8Encoding;
   std::string utf8String("....");
   TextIterator it(utf8String, utf8Encoding);
   TextIterator end(utf8String);
   int n = 0;
   while (it != end) { ++n; ++it; }

      下面是一个完整的例子:

#include "Poco/TextIterator.h"
#include "Poco/UTF8Encoding.h"
using Poco::TextIterator;
using Poco::UTF8Encoding;
int main(int argc, char** argv)
{
	std::string utf8String("This is UTF-8 encoded text.");
	UTF8Encoding utf8;
	TextIterator it(utf8String, utf8);
	TextIterator end(utf8String);
	for (; it != end; ++it)
	{
		int unicode = *it;
	}
	return 0;
}


2.5 其他

      关于编码的其他类还包括了类UTF8和类Unicode。类UTF8实现了UTF8的字符大小转换和比较,当然中文是没有大小的,大小转换只是指英文字符。而类Unicode则可以判断字符是否是Unicode原集中定义的数字,字母等。


2.6 例子

// TextTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "Poco/TextConverter.h"
#include "Poco/Latin1Encoding.h"
#include "Poco/UTF8Encoding.h"
#include "Poco/UTF16Encoding.h"
#include "Poco/UTF8String.h"
#include "Poco/TextIterator.h"
#include "Poco/UTF8Encoding.h"
#include <iostream>
#include <assert.h>

using Poco::TextConverter;
using Poco::Latin1Encoding;
using Poco::UTF8Encoding;
using Poco::UTF16Encoding;
using Poco::UTF8;
using Poco::TextIterator;
using Poco::UTF8Encoding;

#include "Poco/StreamConverter.h"

using Poco::OutputStreamConverter;

void TestConvert()
{
	std::string latin1String("This is Latin-1 encoded text.");
	std::string utf8String;
	Latin1Encoding latin1;
	UTF8Encoding utf8;
	TextConverter converter(latin1, utf8);
	converter.convert(latin1String, utf8String);
	std::cout << utf8String << std::endl;

	std::string latin1StringZ("中国.");
	std::string utf8StringZ;
	UTF16Encoding utf16;
	UTF8Encoding utf8Z;
	TextConverter converterZ(utf16, utf8Z);
	converterZ.convert(latin1StringZ, utf8StringZ);
	std::cout << utf8StringZ << std::endl;
}

void TestStream()
{
	std::string latin1String("This is Latin-1 encoded text.");
	Latin1Encoding latin1;
	UTF8Encoding utf8;
	OutputStreamConverter converter(std::cout, latin1, utf8);
	converter << latin1String << std::endl; // console output will be UTF-8
}

void TestUTF8()
{
	std::string s3("\303\274\303\266\303\244"); // "u"o"a
	UTF8::toUpperInPlace(s3);	
	assert (s3 == "\303\234\303\226\303\204"); // "U"O"A
	UTF8::toLowerInPlace(s3);
	assert (s3 == "\303\274\303\266\303\244"); // "u"o"a
}

void TestIterator()
{
	std::string utf8String("This is UTF-8 encoded text.");
	UTF8Encoding utf8;
	TextIterator it(utf8String, utf8);
	TextIterator end(utf8String);
	int unicode;
	for (; it != end; ++it)
	{
		unicode = *it;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	TestLatinToUtf8();
	TestStream();
	TestUTF8();
	TestIterator();
	return 0;
}


(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8698398

posted @ 2013-03-29 22:27  在天与地之间  阅读(1952)  评论(0编辑  收藏  举报