Unicode编码

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点:

  1. 运行库函数的名称不同
  2. 字符变量的定义不同(char和wchar_t)
  3. Unicode字符串需要在前面加上L
        解决源文件不同版本的问题主要是解决上面3个问题。
       一个解决方案是使用Microsoft Visual C++中的tchar.h头文件,这个头文件是Microsoft自己定义的,不是ANSIC标准的一部分,所以这里面的函数和宏都有一个下划线前缀。
       以tchar.h中的_tcslen函数为例:
#ifdef  _UNICODE
...
#define _tcslen         wcslen
...
#else   /* ndef _UNICODE */
...
#define _tcslen     strlen
...
#endif  /* _UNICODE */

       上面是tchar.h中的内容(里面的宏定义很多,用省略号表示),如果_UNICODE标示符被定义并且tchar.h头文件被包含在程序中_tcslen被定义为wcslen,如果_UNICODE没有被定义,那么_tcslen被定义为strlen。
        这个宏里面包含和C运行库中使用字符串参数的函数,所以这种方式解决上面的问题1.
        然后是数据类型,这个在tchar.h中也解决了:
#ifdef  _UNICODE
...
typedef wchar_t     TCHAR;
...
#else   /* ndef _UNICODE */
...
typedef char     TCHAR;
...
#endif  /* _UNICODE */
        和上面比较类似,这解决了第二个问题。
      最后就是L的问题,这个也是通过宏定义来实现的。如果_UNICODE定义了:__T宏是如下定义的(注意T前面是两个下划线):
#define __T(x) L##x
        如果_UNICODE定义了,那么__T宏的定义如下:
#define __T(x) x

       有两个宏是与__T(x)保持一致的:
#define _T(x) __T(x)
#define _TEXT(x) __T(x)
        这就解决了第3个问题,同时这里也说明了L和_T的关系。

5.宽字符和Windows

      第三部分介绍了C运行库中的宽字符函数,因为Windows既可以执行ASCII,Unicode单写的程序,也可以执行为ASCII和Unicode混合编写的程序,所以Windows中定义的函数也存在和C运行库函数同样地问题,即对应的ASCII版和宽字符版本。
      Windows程序主要是通过windows.h这个头文件来解决Unicode版本和ASCII版本的3个区别这个问题的。
      windows.h头文件中包含windef.h,实际上windows.h最开始包含下面几个重要的头文件:
#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支持功能,这几个头文件是按功能来分类的,注意每个头文件的功能是什么。
      下面来看看winnt.h这个头文件,winnt.h里面有下面的代码:
//
// 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。
      注意在第四部分中解决C运行库函数的宽字节版本时我们解决数据类型的办法是引入tchar.h,并在其中定义了TCHAR,这里要注意tchar.h里面的TCHAR类型和winnt.h(windows.h->windef.h->winnt.h)里面的TCHAR的关系,它们是没有冲突的,它们本质上都表示如果使用Unicode编码,TCHAR会被展开成wchar_t,如果没有使用Unicode编码TCHAR会被展开成char。但是如果在你包含的其他第三方的文件中定义了TCHAR,那么就不能保证不出问题了,所以无论何时在使用其他头文件之前先包含windows.h。
      这样关于数据类型就是第二个问题就解决了,其实还是TCHAR的宏展开而已。
      第三个问题关于L其实上面已经有了答案,就是__TEXT宏(注意前面有两个下划线)。在上面可以看到它的展开式,和C运行库函数时的__T宏是一样的。
      并且紧接上面的代码下面有这个一行:
#define TEXT(quote) __TEXT(quote)   // r_winnt
       这就是TEXT的定义。
      最后是第一个问题,就是函数调用的问题。Windows如何支持Unicode和ASCII方式的函数调用,以MessageBox函数为例。
      MessageBox存在于User32.dll中,当调用MessageBox时,在User32.dll中存在两个入口点,一个名为MessageBoxA(ASCII版),另一个名为MessageBoxW(宽字符版本)。像这样用字符串作为参数的每个win32函数,都在操作系统中存在两个入口点。
      MessageBox在winuser.h的定义如下:
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被展开成那个。这样就解决了第一个也就是函数调用的问题。
      目前为止,解决了C运行库和Windows编程中Unicode和ASCII编码的问题了,针对Unicode和ASCII的三个问题,C运行库和Windows的方法本质上是一样的。
C运行库和Windows对Unicode处理的异同点

 

C运行库

Windows

Unicode宏

_UNICODE

UNICODE

包含的头文件

tchar.h

 Windows.h

字符数据类型

TCHAR

TCHAR

Unicode中L的替代物

_T,__T,_TEXT

__TEXT,TEXT



 
      这个表格做出来以后突然发现这好像就是控制台程序和Win32程序中对字符处理的区别吧,一时的灵感,不知道是不是正确的。

6.Windows中的字符串函数

      windows中也提供了和C运行库中对字符串处理类似的函数,主要有计算字符串长度,复制字符串,连接字符串和比较字符串。
      lstrlen,lstrcpy,lstrcpyn,lstrcat,lstrcmp,lstrcmpi。

7.printf和sprintf

      Windows程序中不能使用printf,这个要记住,printf的替代物是sprintf,sprintf可以实现和printf差不多的功能,甚至可以说比printf更好用。
      这是printf的声明:
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弹出。但是有一个问题需要注意,就是缓冲区的大小必须足够大以容纳这些字符。
      注意sizeof函数返回的字节的数量而不是字符的数量,要返回字符的数量,还需要用它的返回值除以这种数据类型所占的字节数。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值