目录
1.什么是Unicode?
2.为什么使用Unicode?
3.Unicode有什么缺点
4.Unicode编程
4.1 C运行时库对Unicode的支持
4.1.1 字符串类型
4.1.2 字符串处理函数
4.2 用UNICODE宏来编程和控制编译
4.3 编程原则
1.什么是Unicode?
Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS,也叫统一码、万国码、单一码。
Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
虽然我们经常说unicode编码,但它其实不是一种常规意义上的编码,我认为叫作“编号”更准确,unicode给每个字符都定义了一个不会重复的编号,例如:
“汉”对应的编号是0x6C49
因为每个字符都有不会重复的编号,所有unicode可以表示世界上所有的字符,那么有的人会问汉字有7万多个,而unicode只有2个字节,那它是怎么表示得下呢?
早期的Unicode标准有UCS-2、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。每个平面有2^16=65536个码位。unicode规划了17个平面,也就是说一共有17*65536=1114112个码位,可以表示1114112个字符。综上所述,早期的unicode可能确实只有2个字节,但是从UCS-4开始,已经可以支持4个字节了,所以所有的汉字都是能表示得下的。
不要直接把unicode和2个字节划等号,这是不科学的。
那么unicode既然不是一种编码,那编码是什么,我认为编码是一种和计算机通话的规则。例如上面的“汉”字的unicode编号是0x6C49,假设现在有1种编码叫MyUtf,MyUtf会按照它自己的规则把0x6C49的二进制进行一些变换,假设变换成了0x55AA,然后只要告诉计算机当前有个字符用MyUtf来编码的,编码后的值是0x55AA,那么计算机就自动会按照MyUtf的规则进行逆运算,得到0x6C49,并正确的显示一个“汉”字。同样地不管是UTF-8,UTF-16,UTF-32,只要是对UNICODE字符进行编码,那实际上就是对这个编号进行编码,由于编号是固定的,所以不管编码规则如何编号,只要计算机最后能知道你这个编号对应的是UNICODE编号的哪一个就行了,根据编号计算机就能正确的显示出对应的字符,这正是UNICODE标准的意义。
下面是一个用UTF-8对一个UNICODE字符进行编码的示例:
“汉”字的Unicode编码是0x6C49,将0x6C49写成二进制是: 0110 1100 0100 1001
采用UTF-8的编码规则编码后为:1110 0110 1011 0001 0100 1001,即0xE6B189
那么我们能说UTF-8是UNICODE吗?
当然不能了。
UTF-8 是使用互联网上使用最广泛的 unicode 编码方式,目前已经占有整个互联网 92% 的份额。这里再强调下 UTF-8 只是 Unicode 的一种实现方式,UTF-8 是编码方式,而 Unicode 是字符集合。UTF-8它是可变长的编码方式,长度从 1 个字节到 4 个字节不等。它能够完全兼容 ASCII 码,我们知道 ASCII 码 是由 128 个字符组成的,而 Unicode 中的前 128 个字符和 ASCII 码都是对应的。
我们经常用Visual Studio编程,那么它使用的默认编码是什么呢?
答案就是-GB2312
2.为什么使用Unicode?
(1)为了不同语言之间进行数据交换
(2)提高应用程序的运行效率
3.Unicode有什么缺点
(1)Unicode字符串占用的内存是ASCII字符串的两倍
(2)相比ASCII字符串而言比较难以理解,很多数程序员都不熟悉或者是似懂非懂,增加了编程的难度
4.Unicode编程
4.1 C运行时库对Unicode的支持
4.1.1 字符串类型
为了使用Unicode字符串,C运行时库中在string.h中定义了一些数据类型,其中wchar_t用来表示一个Unicode字符串,此类型定义如下:
typedef unsigned short wchar_t;
例如,如果想要创建一个缓存,用于存放最多为9个字符的Unicode字符串和一个结尾为
零的字符,可以使用下面这个语句:
wchar_t buffer[10];
4.1.2 字符串处理函数
以前我们学习C语言时,经常用到strcpy()、strchr()、strcat()等函数,但是这些函数只能处理单字节的字符串,不能正确处理Unicode字符串。为此,ANSI C提供了一组扩展函数,例如:
char *strcat(char*,const char*);
wchar_t *wcscat(wchar_t*,const wchar_t*);int *strcmp(const char*,const char*);
int *wcscmp(const wchar_t*,const wchar_t*);char *strcpy(char*,const char*);
wchar_t *wcscpy(wchar_t*,const wchar_t*);
注意:
unicode函数均以wcs开头,wcs是宽字符串的英文缩写。若要调用unicode函数,只需用前缀wcs来取代ANSI字符串函数的前缀str即可。
4.2 用UNICODE宏来编程和控制编译
对于调用了以str开头的单字节字符串处理函数的代码,或者是调用了以wcs开头的字符串函数的代码,如果我们想做到可以随便选择编译ANSI版本的程序,还是UNICODE版本的程序,这将很麻烦。
为什么呢?原因如下,比如一旦指定要编译ANSI版本的程序,那么程序中的字符串实际都是单字节的了,但是调用了wcs字符串函数的代码却依然把单字节字符串当成多字节的字符串处理,这肯定会引发异常。一旦指定要编译unicode版本的程序,那么程序中的字符串实际都是双字节的了,但是调用了str字符串函数的代码却依然把双字节字符串当成单字节的字符串处理,这也会引发异常。那么有没有一种方法,可以满足只需要做很小的改动,就可让程序编译成ANSI或UNICODE版本,并且能够正常运行呢?
想要实现上面的目标,只需要包含tchar.h即可,而不是包含string.h。tchar.h通过_UNICODE宏来定义不同版本的字符串类型或者字符串函数。当定义_UNICODE宏时,tchar.h会自动定义UNICODE版本的字符串类型和函数,反之则会自动生成ANSI版本的字符串类型和函数。其内部实现如下:
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen#ifdef _UNICODE
#define _T(x) L##x
#else
#define _T(x) x#ifdef _UNICODE
typedef wchar_t TCHAR
#else
typedef char TCHAR
通过上面的说明,我们肯定会想既然这两种写法都有问题,那么正确的打开方式是怎样的呢?
那就是用_TEXT宏或者_T宏来定义字符串,这两个宏的定义如下:
#define _T(x) __T(x)
#define _TEXT(x) __T(x)#ifdef _UNICODE
#define __T(x) L ## x
#else
#define __T(x) x
使用_T宏,可以将上面的代码改为:
TCHAR buff[100] = _T("hello");
这样改后,不管有没有定义_UNICODE,代码都能编译通过。下面再看一下用_T宏来比较字符
if(buff[0] == _T('H'))
{}
4.3 编程原则
为了避免字符集的兼容性问题,下面是应该遵循的一些基本原则:
- 将字符串视为字符数组,而不是char数组或字节数组。
- 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串(通用数据类型可以通过定义宏来选择ANSI或者UNICODE版本)。
- 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。
- 将_TTEXT宏或者_T用于字符串前。
- 执行全局性替换(例如用PTSTR替换PSTR)。
- 修改字符串运算问题
"修改字符串运算问题"的补充说明
计算字符个数时,使用sizeof(buffer)/sizeof(TCHAR),而不是sizeof(buffer)
计算字节数时,使用charNum*sizeof(TCHAR),而不是charNum(字符个数)