再探C++Primer (3)关于unicode和多字节字符集

烂漫一生 提交于 2019-12-09 13:20:27

###ASCⅡ码#

在ASCⅡ码制定的过程中,对于代码是6、7、8位争议不断,由于可靠性的原因,舍弃了6位,而由于成本问题(上世纪60年代来说,8位代价是非常昂贵的),选择了7位编码。于是最后代码共有26个小写字母,26个大写字母,10个数字,32个符号,33个控制码,一个空格码,共128个代码组成了ASCⅡ码,成为了全球标准。

在小型计算机发展时期,8位一字节的标准得到巩固,于是决定以一个字节来储存字符,也就是8位编码,这样就有了128个额外的字符来补充ASCⅡ。

###多字节字符集DBCS#

256字符的字符集难以满足如中文、日文、韩文以及世界各个国家的各种文字需求,于是产生了多字节字符集(DBCS)。DBCS前128个代码就是ASCⅡ码,而较高的128个中有些还跟随有第二个字节,这两个字节在一起表示一个单独的字符,如一个汉字。

DBCS的问题在于,有些字符是单个字节组成的,例如ASCⅡ码,有些字符是两个字节组成的,这导致了字符串的字符长度不能由字节数量决定,字符串的长度需要解析后才知道,每个字节都要被检查是不是双字节的前导字节。

###Unicode字符集#

很明显的是,DBCS是一种很笨的解决方法,也产生了很多问题。作为程序员,我们知道,与其混合使用1字节和2字节代码的DBCS,倒不如用一种全都是2字节16位编码的字符集,这就是Unicode字符集。

Unicode被认为是“宽字符”,每个Unicode的字符是16位而不是8位,在多字节字符集里我们仍处理8位字符,而在Unicode字符集里我们不再处理8位字符。最开始的128个Unicode字符(0x0000到0x007F)是ASCⅡ码,而之后的128个Unicode字符(0x0080到0x00ff)是ASCⅡ码的扩展码。希腊字母表从0x0370到0x03ff,汉语日语韩语从0x3000到0x9FFF代码。

Unicode的优点是只有一个字符集,避免了二义性,而其缺点是内存占比是ASCⅡ的两倍。

现在再来看char和wchar_t

char a = 'A';		//定义了一个被0x41初始化的一个字节存储空间
char *p = "hello!";		//windows是32位系统,指针p占用4个字节空间存储p的地址
						//字符串hello!存储在指针p指向的内存地址及其后6个字节,共7字节
						//包括'h''e''l''l''o''!''\0'
int s = strlen(p);		//s=6; 记住这里

wchar_t wa = 'A';		//定义了一个被0x0041初始化的两个字节存储空间
wchar_t *wp = L"hello!";	//L代表了是宽字节字符,这里,hello!包括最后的\0共占用了14个字节 
int s = strlen(wp);		//s=1; 因为strlen接受的是8位字符,它把h也就是0x0068
						//分为了0x68和0x00(内存存储顺序是高位高地址,低位低地址)
						//当读完0x68后,strlen判断读到0x00也就是字符串结束符,就结束输出了

wchar_t用了2个字节来存储字符,所以大部分处理char类型的函数都不能正常工作了,但是我们有代替的,比如strlen的宽字节版本wcslen。在strlen定义的位置,也就是STRING.H中,strlen的声明如下:

size_t	__cdecl	strlen(cosnt char*);

而wcslen声明在了WCHAR.H中:

size_t	__cdecl	wcslen(const wchar_t*);

所以求wp的长度应该写为:

int s = wcslen(wp);		//求得s=6,即为wp中字符个数

几乎所有的字符函数都有宽字符版本,例如printf的宽字符版本为sprintf,这些都定义在了WCHAR.H中。

###关于TCHAR.H#

Unicode也有很多的缺点,比如空间的占用以及某些方面的不兼容等等,我们希望维护两个版本,一个用ASCⅡ码而另一个用Unicode字符集,但并不希望创建两套源代码文件,所以有了TCHAR.H头文件。TCHAR为需要字符或字符串作为参数的普通库函数(例如printf,strlen,sprintf,wcslen)提供了一系列的替代名称(例如_tprintf,_tcslen),这些名称是“通用”的,因为他们可以指Unicode和非Unicode版本函数。

由于TCHAR不是标准的一部分,所以定义的每个函数和宏都有一个下划线前缀。

定义方式如下:

#ifdef	UNICODE
#define	_tcslen wcslen
#else
#define _tcslen strlen

以此类推其他函数定。

THCAR.H也提供了一个TCHAR类型来解决两个字符数据类型的问题。如果UNICODE标识符被定义了,

typedef	wchar_t	TCHAR;

否则的话,TCHAR就是char

typedef char TCHAR;

关于宽字符字符串前缀L的解决办法如下,如果UNICODE被定义了

#define	__T(x)	L##x

预处理将L和宏参数拼接在一起,例如x是hello,则L##x就是L"hello"

如果UNICODE没有被定义,则__T宏就定义如下:

#define __T(x)	x

然后

#define	_T(x) __T(x)
#define _TEXT(x) __T(x)

所以写字符串的时候只需要将字符串字面写在宏内就可以了

_TEXT("hello!");

这样如果UNICODE被定义了,字符串就解释为宽字符组成的,如果没有被定义,就解释为8位字符组成的。

###关于char16_t和char32_t

随着Unicode的发展,类型wchar_t已经不足以满足需求,比如在字符串编码时,如果有特定的符号和长度特征的类型将很有帮助,而wchar_t的符号和长度不同的C/C++库有不同的规定。因此C++11新增加了char16_t和char32_t。

char16_t:无符号类型,长16位,char32_t无符号类型,长32位

前缀u和U分别指出字符字面值的类型为char16_t和char32_t。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!