Windows程序设计(二)——Unicode简介

来源:互联网 发布:餐饮saas软件 编辑:程序博客网 时间:2024/05/23 00:05

Unicode扩展自ASCII字符集。全16位字符集。

字符集简史

美国标准:美国信息交换标准码(ASCII:American Standard Code for Information Interchange)起始于50年代后期,完成于1967年。26+26+10+32+33+1=128个字符码。

国际方面:美国标准有局限性。

扩展ASCII:

双字节字符集:

Unicode解决方案:

宽字符和C

char数据型态

宽字符:C中的宽字符基于wchar_t数据型态,它在几个表头文件包括WCHAR.H中都有定义,像这样:typedef unsigned short wchar_t;因此wchar_t数据型态与无符号短整数型态相同,都是16位宽。

要定义包含一个宽字符的变量,可使用下面的语句:wchar_t c='A';

可以定义指向宽字符串的指针:wchar_t * p= L "Hello!";\\注意紧接在第一个引号前面的大写字母L(代表‘long’)。这将告诉编译器该字符串按宽字符保存-即每个字符占用2个字节。通常变量p占用4个字节,而字符串变量要14个字节-每个字符要2个,末尾的0还需2个。

同样,可以用下面的语句定义宽字符数组:static wchar_t a[]=L "Hello!";\\该字符串也需要14个字节的存储空间,sizeof(a)将返回14。索引数组a可得到单独的字符a[1]的值是宽字符'e',或者0x0065。

第一个引号前的L很重要,并且在两个符号之间必须没有空格。只有带有L,编译器才知道您需要将字符串存为每个字符2个字节,稍后,当我们看到使用宽字符串而不是变量定义时,您还会遇到第一个引号前面的L。忘了包含L,编译器会提醒。

单个字符文字前面也可使用L前缀,表示他们应解释为宽字符。例如:

wchar_t c= L'A';\\但这不是必要的,C编译器会对该字符进行扩充,使它成为宽字符。

宽字符链接数据库函数

获取字符串的长度,例如定义这样一个字符串指针:char * pc="Hello!";

我们可以呼叫 iLength=strlen (pc);

这时变量iLength将等于6,也就是字符串中的字符数。如果定义一个指向宽字符的指针:wchar_t * pw = L"Hello!";

再次呼叫strlen:iLength = strlen (pw);

麻烦来了。首先C编译器显示警告一条消息,可能是这样的内容:

'function': incompatible type-from 'unsigned short *' to 'const char *'

意思是:声明strlen函数时,该函数应接收char类型的指标,但它现在却接收了一个unsigned short 类型的指标。但可以执行改程序,iLength =1. why?

字符串‘Hello!’中的6个字符占16位;Intel处理器在内存中将其存为:48 00 65 00 6C 00 6F 00 21 00

假定strlen函数正试图得到一个字符串的长度,并把第一个字节作为字符开始计数,但接着假定如果下一个字节是0,则表示字符串结束。

这个小练习清楚的说明C语言本身和执行时期链接数据库函数之间的区别。编译器将字符串L"Hello!"解释为一组16位短整型态数据,并将其保存在wchar_t数组中。编译器还处理数组索引和sizeof操作符,因此这些都能正常工作,但在连结时才添加执行时期链接库函数,例如strlen.这些函数认为字符串由单字节字符组成。遇到宽字符串时,函数就不像我们希望的那样执行了。

但不用麻烦,那些有字符串参数的函数虽需重写,但已经重写完了:

strlen函数的宽字符版是wcslen (wide-character string length :宽字符串长度)。并且在STRING.H(其中也说明了strlen)和WCHAR.H中均有说明。strlen 函数说明如下;size_t_cdecl strlen (const char *);

而wcslen函数则说明如下:size_t_cdecl wcslen (const wchar *);

这时要得到宽字符串的长度可以呼叫 iLength = wcslen (pw);

而函数将返回字符串中的字符数6。记住,改为宽字节后,字符串的字符长度不改变,只是位组长度改变了。

我们所熟悉的所有带有字符串参数的C执行时期链接库函数都有宽字符版。

维护单一原始码

Unicode的缺点:1)程序中每个字符串都将占用两倍的储存空间。2)宽字符执行时期链接库中的函数比常规的函数大。出于这个原因,我们也许想建立两个版本的程序——一个处理ASCII字符串,另一个处理Unicode字符串。最好的解决办法是维护既能按ASCII编译又能按Unicode编译的单一原始码文件。

虽然只是一小段程序,但由于执行时期链接库函数有不同的名称,我们也要定义不同的字符,这在处理前面有L的字符串文字时遇到麻烦。

一个办法就是使用Microsoft Visual C++包含的TCHAR.H文件。该表头文件不是ANSIC标准的一部分,因此那里定义的每个函数和宏定义的前面都有一条底线。TCHAR.H为需要字符串参数的标准执行时期链接库函数提供了一系列的替代名称(例如,_tprintf和_tcslen)。优势这些名称也称为‘通用’函数名称,因为既可指向函数的Unicode版,也可以指向非Unicode版。

如果定义了名为_UNICODE的标识符,并且程序中包含了TCHAR.H表头文件,那么_tcslen就定义为wcslen:

#define _tcslen wcslen

如果没有定义UNICODE,则_tcslen就定义为strlen:

#define _tcelen strlen

TCHAR.H还用了一个新的数据型态TCHAR来解决两种字符数据型态的问题。如果定义了_UNICODE标识符,那么TCHAR就是wchar_t:

typedef wchar_t TCHAR;

否则,TCHAR就是Char:

typedef char TCHAR;

现在开始讨论字符串文字中L问题,如果定义了_UNICODE标识符,那么一个称作_T的宏就定义如下:

#define __T(x) L##x

这是相当晦涩的语法。但合乎ANSI C标准的前置处理器规范。那一对井号称为‘粘贴符号(token paste)’,它将字母L添加到宏参数上。因此,如果宏参数是"Hello!",则L##x就是L"Hello!"。

如果没有定义_UNICODE标识符,则_T宏只简单地定义如下:

#define __T(x) x

此外,还有两个宏与__T定义相同:

#define _ T (x) __T (x)

#define _TEXT (x) __T (x)

在Win32 console 成徐州使用哪个宏,取决于您是喜欢简洁还是详细。基本地,必须按下述方法在_T或_TEXT宏内定义字符串文字:

_TEXT ("Hello!")

这样的话,如果定义了_UNICODE,那么该字符串将解释为宽字符的组合,否则解释为8位的字符字符串。

宽字符和Windows

Windows NT从底层支援Unicode。

试着编写既为ASCII又为Unicode编译的原始码,这是本书所有程序的编写方式。

Windows表头文件类型

WINNT.H定义了新的数据型态,称作CHAR和WCHAR:

typedef char CHAR;

typedef wchar_t  WCHAR;  //  wc

当需要定义8位字符或者16位字符时,在Windows程序中推荐使用的数据形态是CHAR和WCHAR。WCHAR定义后面的注释是匈牙利标记法的建议:一个基于WCHAR数据型态的变量可在前面附加上字母wc以说明一个宽字符。

WINNT.H表头文件进而定义了可用做8位字符串指针的六种数据型态和四个可用做const 8位字符串指针的数据型态。这里精选了表头文件中一些实用的说明数据型态语句:

typedef CHAR * pCHAR, * LPCH,* PCH,*NPSTR,*LPSTR,*PSTR;

typedef CONST CHAR * LPCCH,*PCCH,*LPCSTR,*PCSTR;

前缀N和L表示‘near’和‘long’,指的是16位Windows中两种大小不同的指标。在Win32中near和long没有区别。

类似的,WINNT.H定义了六种可作为16位字符串指针的数据型态和四种可作为const 16位字符串指针的数据型态:

typedef WCHAR * PWCHAR, *LPWCH,*PWCH,*NWPSTR,*LPWSTR,*PWSTR;

typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR;

至此,我们有了数据型态CHAR(一个8位的char)和WCHAR(一个16位的wchar_t),以及指向CHAR和WCHAR的指标。与TCHAR.H一样,WINNT.H将TCHAR定义为一般的字符类型。如果定义了标识符UNICOED(没有底线),则TCHAR和指向TCHAR的指标就分别定义为WCHAR和指向WCHAR的指标;如果没有定义标识符UNICODE,则TCHAR和指向TCHAR的指标就分别定义为char和指向char 的指标:

#ifdef UNICODE

typedef WCHAR TCHAR , * PTCHAR;

typedef LPWSTR LPTCH, PTCH,PTSTR,LPTSTR;

typedef LPCWSTR LPCTSTR;

#else

typedef char TCHAR , * PTCHAR;

typedef LPSTR LPTCH, PTCH , PTSTR,LPTSTR;

typedef LPCSTR LPCTSTR;

#endif

WINNT.H表头文件还定义了一个宏,该宏将L添加到字符串的第一个引号前。如果定义了UNICODE标识符,则一个称作_TEXT的宏定义如下:

#define __TEXT(quote) L##quote

如果没有定义标识符UNICODE,则像这样定义__TEXT宏:

#define __TEXT(quote)quote

此外,TEXT宏还可以这样定义:#define TEXT(quote) __TEXT(quote)

与TCHAR.H中定义_TEXT宏的方法一样。

Windows函数呼叫

从Windows 1.0 到Windows 3.1的16位Windows中,MessageBox函数位于动态链接库USER.EXE。在3.1SDI的WINDOWS.H中,MessageBox函数定义如下:int WINAPI MessageBox (HWND,LPCSTR,LPCSTR,UINT);

注意第二个、第三个参数是指向常数字符串的指针。当编译连结一个Win 16程序时,Windows并不处理MessageBox呼叫,、。程序.EXE文件中的表格,允许Windows将该程序的呼叫与USER中MessageBox函数动态链接起来。

32位的Windows(即所有版本的Windows NT,以及Windows 95、98)除了含有与16位兼容的USER.EXE以外,还含有一个称为USER32.DLL的动态链接库,该动态链接库含有32位使用者接口函数的进入点,,包括32位的MessageBox。

这就是Windows支持Unicode的关键:在USER32.DLL中,没有32位MessageBox函数的进入点。实际上,有两个进入点,一个名为MessageboxA(ASCII版),一个名为MessageBoxW(宽字符版)。用字符串作为参数的每个Win32函数都在操作系统中有两个进入点。但我们不用关心,程序中只用MessageBox就好!

下面是MessageBoxA在WINUSER.H中定义的方法。与早期的MessageBox很相似;

WINUSERAPI int WINAPI MessageBoxA (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UNIT uType);

而MessageBoxW:

WINUSERAPI int WINAPI MessageBoxW (HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption,UNIT uType);

注意MessageBoxW函数的第二个和第三个参数是指向宽字符的指针。

#ifdef UNICODE

#define MessageBox  MessageBoxW

#else

#define MessageBox MessageBoxA

#endif

Windows的字符串函数
Miscrosoft C包括宽字符和需要字符串参数的C语言执行时期链接库函数的所有普通版本。不过,Windows复制了其中一部分。例如下面Windows定义的一组字符串函数,这些函数用来计算字符串长度、复制字符串、连接字符串和比较字符串:
ILength=lstrlen (pString);
pString=lstrcpy(pString1,pString2);
pString=lstrcpyn(pString1,pString2,iCount);
pString=lstrcat(pString1,pString2);
iComp=lstrcmp(pString1,pString2);
iComp=lstrcmpi(pString1,pString2);
在Windows中使用printf
坏消息:在Windows中不能使用printf,可以使用fprintf。虽然Windows程序中可以使用大多数C执行时期链接库-实际上,许多程序写作者更愿意使用C内存管理和文件I/O函数而不是Windows中等效的函数——Windows对标准输入和标准输出没有概念。
好消息:那就是仍然可以使用sprintf及sprintf系列中其它函数来显示文字。除了将内容格式化输出到函数第一个参数所提供的字符串缓冲区以外,其功能与printfI相同。然后便可对该字符串进行操作(例如将其传给MessageBox)。
printf函数说明如下:
int printf(const char * szFotrmat,...);
第一个参数是一个格式字符串,后面是与格式字符串中的代码相对应的不同类型多个参数。
sprintf函数定义如下:
int sprintf(char * szBuffer,const char *szFormat,...);
第一个参数是字符缓冲区;后面是一个格式字符串。Sprintf不是将格式化结果标准输出,而是将其存入szBuffer。该函数返回该字符串的长度。在文字模式程序设计中,printf("The sum of %i and %i is %i ",5,3,5+3);
的功能相同于
char szBuffer[100];
sprintf(szBuffer,"The sum of %i and %i is %i",5,3,5+3);
puts (szBuffer);
在Windows中使用MessageBox显示结果优于puts。
几乎每个人都经历过,当格式字符串与被格式化的变量不合时,可能使printf执行错误并可能造成程序当掉。使用sprintf时,不但要担心这点,而且还有一个新的负担:您定义的字符串缓冲区必须足够大以存放结果。Microsoft专用函数_snprintf解决了这一问题,此函数引进另一个参数,表示以字符计算的缓冲区大小。
vssprintf是sprintf的一个变形,它只有三个参数。用于执行有多个参数的自订函数,类似printf格式。vsprintf的前两个参数与sprintf相同:一个用于保存结果的字符缓冲区和一个格式字符串。第三个参数是指向格式化参数数组的指针。实际上,该指针指向在堆栈中供函数呼叫的变量。va_list\va_start和va_end宏(在STDARG.H中定义)帮助我们处理堆栈指针。本章最后的SCRNSIZE程序展示了使用这些宏的方法。使用vsprintf函数,sprintf函数可以这样编写:
int sprintf(char * szBuffer,const char * szFormat,...)
{
int iReturn;
va_list pArgs;
va_start(pArgs,szFormat);
iReturn=vsprintf (szBuffer,szFormat,pArgs);
va_end (pArgs);
return iReturn;
}
va_start宏将pArg设置为指向一个堆栈变量,该变量地址在堆栈参数szFormat的上面。
由于许多Windows早期程序使用了sprintf和vsprintf,最终导致Microsoft向Windows API 中增添了两个相似的函数。Windows的wsprintf和wvsprintf函数在功能上与sprintf和vsprintf相同,但它们不能处理浮点格式。

Windows程序设计(二)——Unicode简介 - 贝壳 - 大浪淘贝

 

格式化消息框

Windows程序设计(二)——Unicode简介 - 贝壳 - 大浪淘贝

 

Windows程序设计(二)——Unicode简介 - 贝壳 - 大浪淘贝

 

Windows程序设计(二)——Unicode简介 - 贝壳 - 大浪淘贝

 

 程序2-1所示SCRNSIZE程序展示了如何实作MessageBoxPrintf函数,该函数有许多参数并能像printf那样编排它们的格式。
经由从GetSystemMetrics函数得到的消息,该程序以图素为单位显示了视讯显示的宽度和高度。GetSystemMetrics是一个能用来获得Windows中不同对象的尺寸信息的函数。GetSystemMetrics可以用来展示如何在一个Windows窗口中显示和滚动多行文字。

原创粉丝点击