宽字符与Unicode
在C语言中,我们使用char来定义字符,占用一个字节,最多只能表示128个字符,也就是ASCII码中的字符。计算机起源于美国,char 可以表示所有的英文字符,在以英语为母语的国家完全没有问题。
但是世界上存在很多不同的语言,例如汉语、汉语、日语等有成千上万个字符,需要用多个字节来表示,称之为宽字符(Wide Character)。Unicode 是宽字符编码的一种,已经被现代计算机指定为默认的编码方式,Windows 2000以后的操作系统,包括Windows 2000、XP、Vista、Win7、Win8、Win10、Windows Phone、Windows Server 等(它们统称为 Windows NT)都从底层支持Unicode,存取效率比 char 要高。
ASCII码
我们知道,一个二进制位(Bit)有0、1两种状态,一个字节(Byte)有8个二进制位,有256种状态,每种状态对应一个符号,就是256个符号,从00000000到11111111。
计算机诞生于美国,早期的计算机使用者大多使用英文,上世纪60年代,美国制定了一套英文字符与二进制位的对应关系,称为ASCII码,沿用至今。
ASCII码规定了128个英文字符与二进制的对应关系,占用一个字节(实际上只占用了一个字节的后面7位,最前面1位统一规定为0)。例如,字母 a 的的ASCII码为 01100001,那么你暂时可以理解为字母 a 存储到内存之前会被转换为 01100001,读取时遇到 01100001 也会转换为 a。
Unicode编码
随着计算机的流行,使用计算机的人越来越多,不仅限于美国,整个世界都在使用,这个时候ASCII编码的问题就凸现出来了。
ASCII编码只占用1个字节,最多只能表示256个字符,我大中华区10万汉字怎么表示,日语韩语拉丁语怎么表示?所以90年代又制定了一套新的规范,将全世界范围内的字符统一使用一种方式在计算机中表示,这就是Unicode编码(Unique Code),也称统一码、万国码。
Unicode 是一个很大的集合,现在的规模可以容纳100多万个符号,每个符号的对应的二进制都不一样。Unicode 规定可以使用多个字节表示一个字符,例如 a 的编码为 01100001,一个字节就够了,”好“的编码为 01011001 01111101,需要两个字节。
为了兼容ASCII,Unicode 规定前0127个字符与ASCII是一样的,不一样的只是128255的这一段。
在C语言中,使用wchar.h头文件中的wchar_t来定义宽字符,例如:
wchar_t ch = ‘A’;
wchar_t 被定义为typedef unsigned short wchar_t,和一个无符号整型一样,占用两个字节。
如果定义宽字符串,需要加前缀L,例如:
wchar_t *str = L"我爱C语言";
L是必须要加的,并且与字符串之间不能有空格,只有这样编译器才知道每个字符占用两个字节。
宽字符示例:
#include <stdio.h>
#include <wchar.h>
int main(){
char ch = 'A';
wchar_t wch = 'A';
char str[] = "C语言我喜欢";
wchar_t wstr[] = L"C语言我喜欢";
printf("ch=%d, wch=%d, str=%d, wstr=%d\n", sizeof(ch), sizeof(wch), sizeof(str), sizeof(wstr));
return 0;
}
运行结果:
ch=1, wch=2, str=12, wstr=14
wstr 之所以比 str 多两个字节是因为:字符 ‘C’ 占用两个字节,字符串结束标志 ‘\0’ 也占用两个字节。
宽字符串的长度
计算ASCII字符串长度使用 strlen 函数,计算宽字符串长度使用 wcslen 函数:
#include <stdio.h>
#include <wchar.h>
#include <string.h>
int main(){
char str[] = "C语言我喜欢";
wchar_t wstr[] = L"C语言我喜欢";
printf("strlen(str)=%d, wcslen(wstr)=%d\n", strlen(str), wcslen(wstr));
return 0;
}
运行结果:
strlen(str)=11, wcslen(wstr)=6
strlen 的运行结果显然不正确,因为它把一个字节作为一个字符计算,而 wcslen 把两个字节作为一个字符计算。
维护一个版本的源代码
在 Windows NT 以前的操作系统中,甚至包括 Windows 98,对宽字符的支持都不是很好,所以大多情况下使用ASCII编码。Windows NT 推出以后,已经从底层支持了Unicode,所以在 Windows NT 上的程序大多使用Unicode。
如果你希望程序能够在各种版本的Windows操作系统中运行,那么就需要维护两个版本的源代码,ASCII 版和 Unicode 版。ASCII 字符和 Unicode 字符的定义、使用都不一样,要想在一个版本的源代码中做兼容处理会非常困难,要做大量的工作,对程序员来说简直是噩梦。
不过,Windows 又为我们做了一件好事,已经处理了兼容性问题。它是怎么做到的呢?
例如对于字符串,ASCII 中使用 char 来定义,而 Unicode 中使用 wchar_t 来定义,并且需要添加前缀L。那么在 windows.h 头文件中(或者是它包含的其他头文件)就这样来处理:
#ifdef UNICODE
typedef wchar_t TCHAR;
#define TEXT(quote) L##quote
#else
typedef char TCHAR
#define TEXT(quote) quote
#endif
我们在源码中可以这样来使用:
TCHAR str[] = TEXT(“C语言我喜欢”);
如果是Unicode版,也就是定义了UNICODE宏,那么上面的语句等价于:
wchar_t str[] = L"C语言我喜欢";
如果是ASCII,也就是没有定义UNICODE宏,那么等价于:
char str[] = “C语言我喜欢”;
在Windows中,随处可见这样的处理。虽然现代操作系统都已经支持Unicode,无需再考虑与ASCII的兼容性问题,但是依然要为这些历史问题付出代价。