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

这篇博客详细介绍了字符编码的概念,包括ASCII码、Unicode、UCS-2、UCS-4、BMP、UTF编码等。重点讨论了Unicode和UTF-8的区别以及在POCO C++库中的实现,包括字符原集和表示之间的转换类、字符集之间的转换以及相关类的使用示例。
摘要由CSDN通过智能技术生成

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( Character Encoding Form)有两种格式: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数
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C++ 中,使用 Poco 的 `Base64Decoder` 类解码 UTF-8 编码字符串,可以按照以下步骤进行: 1. 将 UTF-8 编码字符串转换为 UTF-16 编码字符串。 ```c++ #include <string> #include <codecvt> std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter; std::wstring utf16Str = converter.from_bytes(utf8Str); // utf8Str 是 UTF-8 编码字符串 ``` 在上述代码中,我们使用 `std::wstring_convert` 类和 `std::codecvt_utf8_utf16` 类型的模板参数将 UTF-8 编码字符串转换为 UTF-16 编码字符串。需要注意的是,上述代码需要包含头文件 `<string>` 和 `<codecvt>`。 2. 使用 `Base64Decoder` 类解码 UTF-16 编码字符串。 ```c++ #include <sstream> #include "Poco/Base64Decoder.h" std::wstring result; std::wstringstream ss; Poco::Base64Decoder decoder(ss); decoder << utf16Str; // utf16Str 是 UTF-16 编码字符串 decoder.close(); ss >> result; ``` 在上述代码中,我们使用 `std::wstringstream` 类将 UTF-16 编码字符串写入到 `Base64Decoder` 类的输入流中,然后通过 `decoder.close()` 方法关闭输入流,并将解码后的结果写入到 `std::wstring` 类型的变量 `result` 中。 最后,我们可以将解码后的 UTF-16 编码字符串再次转换为 UTF-8 编码字符串: ```c++ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> utf8converter; std::string utf8Result = utf8converter.to_bytes(result); ``` 在上述代码中,我们使用 `std::wstring_convert` 类和 `std::codecvt_utf8_utf16` 类型的模板参数将 UTF-16 编码字符串转换为 UTF-8 编码字符串,然后将结果存储到 `utf8Result` 变量中。需要注意的是,上述代码需要包含头文件 `<string>` 和 `<codecvt>`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值