1.Unicode与双字节字符集(DBCS)的区别
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只是宽字符编码的一种实现。
2.char与wchar_t
C语言中关于字符有两种数据类型来表示。就是char和wchar_t,这两种数据类型都在C语言中定义了的。首先要搞清楚它们的关系。
通常在C语言中见到的表示字符的方式如下:
char c='A';
char * p="Hello!";
第一行代码声明定义和初始化了一个包含单个字符的变量。变量c需要一个字节的存储空间而且会用十六进制0x41来被初始化,也就是ASCII字母表中A的符号。
第二行代码中定义一个字符指针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表示长整型,这向编译器表明这个字符串将用宽字符存储。
3.宽字符函数
当使用char时可以这样使用
char * pc ="Hello!";
int iLength=strlen(pc);
很明显iLength的值为6.
如果使用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语言中使用字符串串参数的运行库函数都有宽字节的版本,这个查阅一下就可以了。
4.维护一个源代码的Unicode版和ASCII版
Unicode和ASCII相比有它的好处,就是能够处理更多的符号,但是也有它的缺点,就是字符串会占用两倍的存储空间。所以维护一个源文件的Unicode版本和ASCII版本是一个非常实用的技巧。
Unicode版本和ASCII版本在主要的区别有3点:
- 运行库函数的名称不同
- 字符变量的定义不同(char和wchar_t)
- Unicode字符串需要在前面加上L
#ifdef _UNICODE
...
#define _tcslen wcslen
...
#else /* ndef _UNICODE */
...
#define _tcslen strlen
...
#endif /* _UNICODE */
上面是tchar.h中的内容(里面的宏定义很多,用省略号表示),如果_UNICODE标示符被定义并且tchar.h头文件被包含在程序中_tcslen被定义为wcslen,如果_UNICODE没有被定义,那么_tcslen被定义为strlen。
#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的关系。
5.宽字符和Windows
#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被展开成那个。这样就解决了第一个也就是函数调用的问题。
|
6.Windows中的字符串函数
7.printf和sprintf
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弹出。但是有一个问题需要注意,就是缓冲区的大小必须足够大以容纳这些字符。