摘自 见钱眼开的专栏
Unicode 简介
见钱眼开 于2005-3-1
为什么需要 Unicode 编码?因为 ASCII 编码无法表示足够多的字符。在 ASCII 编码中,每个字符用 7 位表示,可以表示 128 个不同字符;在 Unicode 中,每个字符用 16 位表示,可以表示 216 即 65536 个不同字符。
为什么双字节字符集( DBCS : double-byte character set )不能完全满足需要?在双字节字符集中,最初的 128 个编码( 0 - 127 )就是 ASCII ,从 128 ( 0x80 )开始,由两个字节组成一个字符。这就带来了附加的编程问题。难以凭字节数决定字符数,难以进行字符串指针操作。而 Unicode 所有字符都是两字节的,不会带来编程中的问题,唯一不足就是会占用更多的磁盘空间。
在 C 程序中,使用 char 类型以 1 字节长度定义存储 1 个字符,如:
char c = ‘A’; // 变量 c 大小为 1 字节,存储内容为 0x41
对于 Unicode 字符,采用 wchar_t 类型以 2 个字节长度定义存储 1 个字符。如:
wchar_t d = L’A’; // 变量 d 大小为 2 字节,存储内容为 0x0041
其实 wchar_t 和无符号短整型等价,都是 16 位宽:
typedef unsigned short wchar_t;
至于字符串前的大写字母 L ,是个修饰符号,告诉编译器要为该字符串每个字符分配 2 字节内存。
对于宽字符, ANSI C 标准提供了新的字符处理库函数,这些函数在 string.h 和 wchar.h 中都有声明。例如原 strlen 函数,对应的宽字符版本函数原型声明如下:
size_t __cdecl wcslen(const wchar_t* );
但这样带来的问题是如何在一个程序中同时可以处理两种字符类型呢?
一个办法是使用 Visual C++ 包含的 tchar.h 头文件,该头文件不是 ANSI C 标准一部分,所以每个定义的函数和宏定义前都一个条下划线。 如果定义了 _UNICODE 标识符,进行下列宏定义:
#define _tcslen wcslen
否则定义:
#define _tcslen strlen
在 tchar.h 头文件中还增加了一种 TCHAR 类型来解决两种字符类型的问题。如果定义了 _UNICODE 标识符,进行下列类型等价声明:
typedef wchar_t TCHAR;
#define __T(x) L##x 或者 #define _T(x) L##x 或者 #define _TEXT(x) L##x
否则声明:
typedef char TCHAR;
#define __T(x) x 或者 #define _T (x) x 或者 #define _TEXT(x) x
以上这些都是基于 C 运行时库的声明定义,在 Windows 下 Microsoft 重新定义了新的数据类型和函数 , 可以在 winnt.h 头文件中我们可以看到:
typedef char CHAR
typedef wchar_t WCHAR
typedef CHAR *PCHAR,*LPCH,*PCH,*NPSTR,*LPSTR,*PSTR;
typedef CONST CHAR *LPCCH,*PCCH,*LPCSTR,*PCSTR;
typedef WCHAR *PWCHAR,*LPWCH,*PWCH,*NPWSTR,*LPWSTR,*PWSTR;
typedef CONST WCHAR *LPCWCH,*PCWCH,*LPCWSTR,*PCWSTR;
如果定义了 UNICODE 标识符(没有下划线),则将 TCHAR 和指向 TCHAR 指针定义为 WCHAR 和指向 WCHAR 指针,否则定义为 CHAR 和指向 CHAR 指针。此外,在 Winnt 头文件中还增加了一个宏,就是将 L 添加到字符串的第一个引号前。
#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT(quote) quote
#endif
windows NT 完全支持 Unicode 。因此大多数 API 函数都存在两种版本声明定义,通常根据 UNICODE 标识符来定位不同函数入口点。
C 运行时库函数已经提供完善的字符处理功能,但为了更方便 windows 下程序开发的需要, Windows 中重新定义了一些类似的字符处理函数。
遗憾的是, printf 在 windows 程序中已经不能使用。我们仍可以使用 sprintf 和 vsprintf 。 printf 函数声明如下:
int printf(const char* pszFormat,…);
首个参数是格式字符串,后面是对应格式字符串的若干个参数
int sprintf(char* pszBuffer,const char* pszFormat,…);
首个参数是字符缓冲区,其他参数和 printf 函数参数含义一致。 Sprintf 函数将结果写入字符缓冲区。
int __cdecl vsprintf(char * pszBuffer, const char * pszFormat, va_list ArgsList);
首个参数是字符缓冲区,第二个参数是格式字符串,最后一个参数是指向格式化参数数组的指针,实际上该指针指向在堆栈中供函数调用的变量。
Vsprintf 函数使用实例如下:
int __cdecl ShowMessage(char* pzsCaption,char* pszFormat,…)
{
char* pszBuffer[256];
va_list pArgList;
va_start(pArgList,pszFormat);
vsprintf(pszBuffer,pszFormat,pArgList);
va_end(pArgList);
return MesageBox(NULL,pszBuffer,szCaption,0);
}
使用 sprintf 和 vsprintf 函数的问题是无法保证字符缓冲区不溢出。
_snprintf 函数通过增加一个缓冲区可容纳字符数大小参数解决了以上问题。