Unicode字符集和多字节字符集关系(转)

 在计算机中字符通常并不是保存为图像,每个字符都是使用一个编码来表示的,而每个字符究竟使用哪个编码代表,要取决于使用哪个字符集(charset)。 

多字节字符集:

  在最初的时候,Internet上只有一种字符集——ANSI的ASCII字符集,它使用7 bits来表示一个 字符,总共表示128个字符,其中包括了 英文字母、数字、标点符号等常用字符。之后,又进行扩展,使用8 bits表示一个字符,可以表示256个字符,主要在原来的7 bits字符集的基础上加入了一些特殊符号。后来,由于各国语言的加入,ASCII已经不能满足信息交流的需要,为了能够表示其它国家的文字,各国在 ASCII的基础上制定了自己的字符集,这些从ANSI标准派生的字符集被习惯的统称ANSI字符集,它们正式的名称应该是MBCS(Multi-Byte Chactacter System,即多字节字符系统)。这些派生字符集的特点是以ASCII 127 bits为基础,兼容ASCII 127,他们使用大于128的编码作为一个Leading Byte,紧跟在Leading Byte后的第二(甚至第三)个字符与 Leading Byte一起作为实际的编码。这样的字符集有很多,我们常见的GB-2312就是其中之一。
       例如在GB-2312字符集中,“连通”的编码为C1 AC CD A8,其中C1和CD就是Leading Byte。前127个编码为标准ASCII保留,例如“0”的编码是30H(30H表示十六进制的30)。软件在读取时,如果看到30H,知道它小于128就是标准ASCII,表示“0”,看到C1大于128就知道它后面有一个另外的编码,因此C1 AC一同构成一个整个的编码,在GB-2312字符集中表示“连”。 

Unicode字符集:

  Unicode的学名 是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。UCS只是规定如何编码,并没有规定如何传输、保存这个编码。UTF是“UCS Transformation Format”的缩写。

  Unicode字符集有多种编码形式,它固定使用16 bits(两个字节、一个字)来表示一个字符,共可以表示65536个字符。将世界上几乎所有语言的常用字符收录其中,方便了信息交流。标准的Unicode称为UTF-16。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8(注意UTF-8是编码,它属于Unicode字符集),使用类似MBCS的方式对Unicode进行编码。UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元。
       Unicode字符集有多种编码形式,而ASCII只有一种,大多数MBCS(包括GB-2312)也只有一种。
       
Unicode的最初目标,是用1个16位的编码来为超过65000字符提供映射。但这还不够,它不能覆盖全部历史上的文字,也不能解决传输的问题 (implantation head-ache's),尤其在那些基于网络的应用中。已有的软件必须做大量的工作来程序16位的数据。因此,Unicode用一些基本的保留字符制定了三套编码方式。它们分别是UTF-8,UTF-16和UTF-32

  UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian),UTF-16需要通过在文件开头以名为BOM(Byte Order Mark)的字符来表明文件是Big Endian还是Little Endian。Unicode规范中推荐的标记字节顺序的方法是BOM(Byte Order Mark)。在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
       正如名字所示,在UTF-8中,字符是以8位序列来编码的,用一个或几个字节来表示一个字符。这种方式的最大好处,是UTF-8保留了ASCII字符的编码做为它的一部分,例如,在UTF-8和ASCII中,“A”的编码都是0x41.UTF-16和UTF-32分别是Unicode的16位和32位编码方式。考虑到最初的目的,通常说的Unicode就是指UTF-16。 

  UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
       例如“连通”两个字的Unicode标准编码UTF-16 (big endian)为:DE 8F 1A 90 
而其UTF-8编码为:E8 BF 9E E9 80 9A 
最后,当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件有三种途径来决定文本的字符集和编码: 
最标准的途径是检测文本最开头的几个字节,如下表: 
开头字节 Charset/encoding 
EF BB BF UTF-8 
FE FF UTF-16/UCS-2, little endian 
FF FE UTF-16/UCS-2, big endian 
FF FE 00 00 UTF-32/UCS-4, little endian.

00 00 FE FF UTF-32/UCS-4, big-endian.例如插入标记后,连通”两个字的UTF-16 (big endian)和UTF-8码分别为: 
FF FE DE 8F 1A 90 
EF BB BF E8 BF 9E E9 80 9A 

big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。 

对于为什么使用Unicode?(以下引自《windows核心编程》)
  开发应用程序的时候,强烈建议你使用Unicode字符和字符串,理由如下:

  • Unicode使程序的本地化变得更容易
  • 使用Unicode,只需发布一个二进制(.exe或DLL)文件,即可支持所有语言
  • Unicode代码执行速度更快,占用内存更少,提升了应用程序的效率。自从Windows2K开始,Win的系统内核开始完全支持并完全应用Unicode编写,所有ANSI字符在进入底层前都会被相应的API转换成Unicode。所以,如果你一开始就使用Unicode,则可以减少转换的用时和RAM开销。
    Windows CE 本身就是使用Unicode的一种操作系统,完全不支持ANSI Windows函数;
    Windows 98 只支持ANSI,只能为ANSI开发应用程序。 
  • 使用Unicode,你的应用程序能轻松调用所有不反对使用(nondeprecated)的Windows函数,因为一些Windows函数提供了只能处理Unicode字符和字符串的版本;
  • 使用Unicode,你的代码很容易与COM集成(后者要求使用Unicode字符和字符串);
  • 使用Unicode,你的代码很容易与.NET Framework集成(后者要要求使用Unicode字符和字符串);
  • 使用Unicode,能保证你的代码能够轻松操纵你自己的资源(其中的字符串总是Unicode的);
  • 世界上大多数程序用的字符集都是Unicode,因为Unicode有利于程序国际化和标准化

Unicode :宽字节字符集 
1. 如何取得一个既包含单字节字符又包含双字节字符的字符串的字符个数? 
可以调用Microsoft Visual C++的运行期库包含函数_mbslen来操作多字节(既包括单字节也包括双字节)字符串。 
调用strlen函数,无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。 

2. 如何对DBCS(双字节字符集)字符串进行操作? 
函数 描述 
PTSTR CharNext ( LPCTSTR ); 返回字符串中下一个字符的地址 
PTSTR CharPrev ( LPCTSTR, LPCTSTR ); 返回字符串中上一个字符的地址 
BOOL IsDBCSLeadByte( BYTE ); 如果该字节是DBCS字符的第一个字节,则返回非0值 

3. 为什么要使用Unicode? 
(1) 可以很容易地在不同语言之间进行数据交换。 
(2) 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件。 
(3) 提高应用程序的运行效率。 
Windows 2000是使用Unicode从头进行开发的,如果调用任何一个Windows函数并给它传递一个ANSI字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序更加有效地运行。 
Windows CE 本身就是使用Unicode的一种操作系统,完全不支持ANSI Windows函数
Windows 98 只支持ANSI,只能为ANSI开发应用程序。 
Microsoft公司将COM从16位Windows转换成Win32时,公司决定需要字符串的所有COM接口方法都只能接受Unicode字符串。 

4. 如何编写Unicode源代码? 
Microsoft公司为Unicode设计了WindowsAPI,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码文件,以便使用或者不使用Unicode来对它进行编译。只需要定义两个宏(UNICODE和_UNICODE),就可以修改然后重新编译该源文件。 
_UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常必须同时定义这两个宏。 

VC里面项目属性可选UNICODE 还是多字符集合;

5. Windows定义的Unicode数据类型有哪些? 
数据类型 说明 
WCHAR Unicode字符 
PWSTR 指向Unicode字符串的指针 
PCWSTR 指向一个恒定的Unicode字符串的指针 
对应的ANSI数据类型为CHAR,LPSTR和LPCSTR。 
ANSI/Unicode通用数据类型为TCHAR,PTSTR,LPCTSTR。 

6. 如何对Unicode进行操作? 
字符集 特性 实例 
ANSI 操作函数以str开头 strcpy 
Unicode 操作函数以wcs开头 wcscpy 
MBCS 操作函数以_mbs开头 _mbscpy

ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运行期库) 
ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
所有新的和未过时的函数在Windows2000中都同时拥有ANSI和Unicode两个版本。ANSI版本函数结尾以A表示;Unicode版本函数结尾以W表示。Windows会如下定义: 
#ifdef UNICODE 
#define CreateWindowEx CreateWindowExW 
#else 
#define CreateWindowEx CreateWindowExA 
#endif // !UNICODE 

7. 如何表示Unicode字符串常量? 
字符集 实例 
ANSI “string” 
Unicode L“string” 
ANSI/Unicode T(“string”)或_TEXT(“string”)if( szError[0] == _TEXT(‘J’) ){ } 

8. 为什么应当尽量使用操作系统函数? 
这将有助于稍稍提高应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe所使用。由于这些函数使用得很多,因此,在应用程序运行时,它们可能已经被装入RAM。 
如:StrCat,StrChr,StrCmp和StrCpy等。
 
9.如何编写符合ANSI和Unicode的应用程序? 
(1) 将文本串视为字符数组,而不是chars数组或字节数组。 
(2) 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串。 
(3) 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。 
(4) 将TEXT宏用于原义字符和字符串。 
(5) 执行全局性替换(例如用PTSTR替换PSTR)。 
(6) 修改字符串运算问题。例如函数通常希望在字符中传递一个缓存的大小,而不是字节。
这意味着不应该传递sizeof(szBuffer),而应该传递(sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那么请记住要按字节来分配内存。这就是说,应该调用malloc(nCharacters *sizeof(TCHAR)),而不是调用malloc(nCharacters)。 

10. 如何对字符串进行有选择的比较? 
通过调用CompareString来实现。 
标志 含义 
NORM_IGNORECASE 忽略字母的大小写 
NORM_IGNOREKANATYPE 不区分平假名与片假名字符 
NORM_IGNORENONSPACE 忽略无间隔字符 
NORM_IGNORESYMBOLS 忽略符号 
NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一个字符 
SORT_STRINGSORT 将标点符号作为普通符号来处理 

11. 如何判断一个文本文件是ANSI还是Unicode? 
判断如果文本文件的开头两个字节是0xFF和0xFE,那么就是Unicode,否则是ANSI。 


12. 如何判断一段字符串是ANSI还是Unicode? 
IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode有可能返回不正确的结果。 

13. 如何在Unicode与ANSI之间转换字符串? 
Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。

你可能感兴趣的:(----C/C++)