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
|
|
例如“汉”字的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个字节表示文字符号。
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编码。
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类中,原集和其表现也一致,所以也不存在转换问题。
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; };
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)