Unicode被认为是“宽字符”(特别是在C环境中)。Unicode中每个字符是16位宽而不是8位宽。8位宽在Unicode中时无意义的。双字节字符集中有些字符是8位宽(ASCII字符),而还有一些字符是16位宽。
Unicode中前128个Unicode字符(16位码从0x0000到0x007F)是ASCII码
之后的128个Unicode字符(从0x0080到0x00FF)是ISO 8859-1 ASCII扩展码
希腊字母从0x0370到0x03FF
汉语,日语,韩语的象形文字从0x3000到0x9FFF
宽字符并不一定是Unicode。Unicode只是宽字符编码的一种实现。
C语言中关于字符有两种数据类型来表示。就是char和wchar_t,这两种数据类型都在C语言中定义了的。首先要搞清楚它们的关系。
通常在C语言中见到的表示字符的方式如下:
char c='A'; char * p="Hello!";
第二行代码中定义一个字符指针p,在32位windows系统中需要4个字节的存储空间,在64位windows系统中需要8个字节的存储空间。字符串“Hello!”存储在静态内存中并使用7个字节的存储空间,其中6个字节存储字符串而另外一个字节存储表示字符串结束的0.
上面是对char这种数据类型的分析,这种数据类型使用的前提是你使用ASCII编码,很多C语言的书籍中一开始就假定了本书中使用的是ASCII编码(又或许C中本来大多数情况下都是使用ASCII编码),所以我们一般见到的都是用char来表示字符。
下面来看看wchar_t:
当使用Unicode编码时,表示字符需要用两个字节,这时就不能使用char了,表示字符就需要使用wchar_t。wchar_t这个数据类型被定义在多个头文件中,包括wctype.h,如下所示:
typedef unsigned short wchar_t;它的使用方法和char类似,只不过它表示的宽字符,就是每一个字符会占用2个字节。
wchar_t c="A"; wchar_t * p=L"Hello!";第一行代码中c现在是一个两个字节的值0x0041,这是Unicode中字母A的表示,并且它在内存中的存储顺序为:0x41,0x00(这个次序非常重要)。
第二行代码定义了一个指向宽字符的指针,注意“Hello!”前面有了个L,这个L表示长整型,这向编译器表明这个字符串将用宽字符存储。
当使用char时可以这样使用
char * pc ="Hello!"; int iLength=strlen(pc);
如果使用wchar_t,还是要这个函数:
wchar_t * pw =L"Hello!"; int iLength=strlen(pw);代码会报错:
error C2664: “strlen”: 不能将参数 1 从“wchar_t *”转换为“const char *”在《Windows程序设计》这本书中作者指出C编译器会给出一个警告,但是我在编译时弹出了上面的错误,估计是visual studio改变的原因。
从上面结果中不难看出strlen不能用来处理宽字符。它的替代函数式wcslen(“宽字符字符串长度”)。
wchar_t * pw =L"Hello!"; int iLength=wcslen(pw);测试后返回的结果也是6。wcslen函数定义在string.h
strlen和wcslen的声明如下(在string.h),wcslen在wchar.h中也有如下的声明
size_t __cdecl strlen(_In_z_ const char * _Str); size_t __cdecl wcslen(_In_z_ const wchar_t * _Str);注意:所以C语言中使用字符串串参数的运行库函数都有宽字节的版本,这个查阅一下就可以了。
Unicode和ASCII相比有它的好处,就是能够处理更多的符号,但是也有它的缺点,就是字符串会占用两倍的存储空间。所以维护一个源文件的Unicode版本和ASCII版本是一个非常实用的技巧。
Unicode版本和ASCII版本在主要的区别有3点:
#ifdef _UNICODE ... #define _tcslen wcslen ... #else /* ndef _UNICODE */ ... #define _tcslen strlen ... #endif /* _UNICODE */
#ifdef _UNICODE ... typedef wchar_t TCHAR; ... #else /* ndef _UNICODE */ ... typedef char TCHAR; ... #endif /* _UNICODE */和上面比较类似,这解决了第二个问题。
#define __T(x) L##x如果_UNICODE定义了,那么__T宏的定义如下:
#define __T(x) x
#define _T(x) __T(x) #define _TEXT(x) __T(x)这就解决了第3个问题,同时这里也说明了L和_T的关系。
#include <windef.h> #include <winbase.h> #include <wingdi.h> #include <winuser.h>windef.h头文件中有许多在WIndows中使用的基本数据类型的定义,同时在windef.h内包含winnt.h
#ifndef NT_INCLUDED #include <winnt.h> #endif /* NT_INCLUDED */winnt.h头文件负责处理基本的Unicode支持功能,这几个头文件是按功能来分类的,注意每个头文件的功能是什么。
// // Basics // #ifndef VOID #define VOID void typedef char CHAR; typedef short SHORT; typedef long LONG; #if !defined(MIDL_PASS) typedef int INT; #endif #endif // // UNICODE (Wide Character) types // #ifndef _MAC typedef wchar_t WCHAR; // wc, 16-bit UNICODE character #else // some Macintosh compilers don't define wchar_t in a convenient location, or define it as a char typedef unsigned short WCHAR; // wc, 16-bit UNICODE character #endif我们只需要注意上面的两行代码:
typedef char CHAR; typedef wchar_t WCHAR;这表示windows又给我们定义了两种数据类型CHAR和WCHAR,他们分别用来定义8位和16位字符。同时也定义了一些这些类型所对应的指针类型。如下:
// // ANSI (Multi-byte Character) types // typedef CHAR *PCHAR, *LPCH, *PCH; typedef CONST CHAR *LPCCH, *PCCH; // // UNICODE (Wide Character) types // typedef WCHAR *PWCHAR, *LPWCH, *PWCH; typedef CONST WCHAR *LPCWCH, *PCWCH;上面这些准备工作做好以后可以解决Unicode和ASCII编码中的第二个问题和第三个问题了,就是数据类型的问题和Unicode编码有个L的问题了,在winnt.h有下面的宏定义:
// // Neutral ANSI/UNICODE types and macros // #ifdef UNICODE // r_winnt #ifndef _TCHAR_DEFINED typedef WCHAR TCHAR, *PTCHAR; typedef WCHAR TBYTE , *PTBYTE ; #define _TCHAR_DEFINED #endif /* !_TCHAR_DEFINED */ typedef LPWCH LPTCH, PTCH; typedef LPWSTR PTSTR, LPTSTR; typedef LPCWSTR PCTSTR, LPCTSTR; typedef LPUWSTR PUTSTR, LPUTSTR; typedef LPCUWSTR PCUTSTR, LPCUTSTR; typedef LPWSTR LP; #define __TEXT(quote) L##quote // r_winnt #else /* UNICODE */ // r_winnt #ifndef _TCHAR_DEFINED typedef char TCHAR, *PTCHAR; typedef unsigned char TBYTE , *PTBYTE ; #define _TCHAR_DEFINED #endif /* !_TCHAR_DEFINED */ typedef LPCH LPTCH, PTCH; typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR; typedef LPCSTR PCTSTR, LPCTSTR, PCUTSTR, LPCUTSTR; #define __TEXT(quote) quote // r_winnt #endif /* UNICODE */ // r_winnt主要的意思就是如果定义了UNICODE宏,那么TCHAR和指向TCHAR的指针会被定义为WCHAR和指向WCHAR的指针,如果UNICODE宏没有被定义,那么TCHAR和指向TCHAR的指针会被定义成char。
#define TEXT(quote) __TEXT(quote) // r_winnt这就是TEXT的定义。
WINUSERAPI int WINAPI MessageBoxA( __in_opt HWND hWnd, __in_opt LPCSTR lpText, __in_opt LPCSTR lpCaption, __in UINT uType); WINUSERAPI int WINAPI MessageBoxW( __in_opt HWND hWnd, __in_opt LPCWSTR lpText, __in_opt LPCWSTR lpCaption, __in UINT uType); #ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif // !UNICODE上面的代码非常清晰,首先是MessageBoxW和MessageBoxA的声明,最后根据是否定义UNICODE宏来判断MessageBox被展开成那个。这样就解决了第一个也就是函数调用的问题。
|
int printf( const char * _Format, ...);这是sprintf的声明:
int sprintf(char * szBuffer, const char * _Format, ...);就多了个参数,printf是将字符输出到控制台中,而sprintf是将字符输出到缓冲区中。
char sz[100]; sprintf(sz,"Hello world ! %s","I am very happy!"); puts(sz);这是它的使用方法,功能和printf一样,但是将格式化的数据保存在sz中我们就可以做其他事了,比如使用MessageBox弹出。但是有一个问题需要注意,就是缓冲区的大小必须足够大以容纳这些字符。