Unicode编码

论坛 期权论坛 编程之家     
选择匿名的用户   2021-5-31 21:38   11   0

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函数返回的字节的数量而不是字符的数量,要返回字符的数量,还需要用它的返回值除以这种数据类型所占的字节数。









分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:3875789
帖子:775174
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP