⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 2.unicode简介.txt

📁 本书介绍了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程序写作的方法
💻 TXT
📖 第 1 页 / 共 3 页
字号:
我们可以呼叫

iLength = strlen (pc) ;
        
这时变量iLength将等于6,也就是字符串中的字符数。

太好了!现在让我们试着定义一个指向宽字符的指针:

wchar_t * pw = L"Hello!" ;
        
再次呼叫strlen :

iLength = strlen (pw) ;
        
现在麻烦来了。首先,C编译器会显示一条警告消息,可能是这样的内容:

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

这条消息的意思是:声明strlen函数时,该函数应接收char类型的指标,但它现在却接收了一个unsigned short类型的指标。您仍然可编译并执行该程序,但您会发现iLength等于1。为什么?

字符串「Hello!」中的6个字符占用16位:

0x0048 0x0065 0x006C 0x006C 0x006F 0x0021
Intel处理器在内存中将其存为:

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

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

您可能要说:「噢,太麻烦了!」现在每个C语言链接库函数都必须重写以接受宽字符。但事实上并不是每个C语言链接库函数都需要重写,只是那些有字符串参数的函数才需要重写,而且也不用由您来完成。它们已经重写完了。

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_t *) ;        
这时我们知道,要得到宽字符串的长度可以呼叫

iLength = wcslen (pw) ; 
函数将返回字符串中的字符数6。请记住,改成宽字节后,字符串的字符长度不改变,只是位组长度改变了。

您熟悉的所有带有字符串参数的C执行时期链接库函数都有宽字符版。例如,wprintf是printf的宽字符版。这些函数在WCHAR.H和含有标准函数说明的表头文件中说明。

维护单一原始码


当然,使用Unicode也有缺点。第一点也是最主要的一点是,程序中的每个字符串都将占用两倍的储存空间。此外,您将发现宽字符执行时期链接库中的函数比常规的函数大。出于这个原因,您也许想建立两个版本的程序-一个处理ASCII字符串,另一个处理Unicode字符串。最好的解决办法是维护既能按ASCII编译又能按Unicode编译的单一原始码文件。

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

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

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

#define _tcslen wcslen        
如果没有定义UNICODE,则_tcslen定义为strlen:

#define _tcslen 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。这意味着Windows NT内部使用由16位字符组成的字符串。因为世界上其它许多地方还不使用16位字符串,所以Windows NT必须经常将字符串在操作系统内转换。Windows NT可执行为ASCII、Unicode或者ASCII和Unicode混合编写的程序。即,Windows NT支持不同的API函数呼叫,这些函数接受8位或16位的字符串(我们将马上看到这是如何动作的。)

相对于Windows NT,Windows 98对Unicode的支持要少得多。只有很少的Windows 98函数呼叫支持宽字符串(这些函数列在《Microsoft Knowledge Base article Q125671》中;它们包括MessageBox)。如果要发行的程序中只有一个.EXE文件要求在Windows NT和Windows 98下都能执行,那么就不应该使用Unicode,否则就不能在Windows 98下执行;尤其程序不能呼叫Unicode版的Windows函数。这样,将来发行Unicode版的程序时会处于更有利的位置,您应试着编写既为ASCII又为Unicode编译的原始码。这就是本书中所有程序的编写方式。

Windows表头文件类型


正如您在第一章所看到的那样,一个Windows程序包括表头文件WINDOWS.H。该文件包括许多其它表头文件,包括WINDEF.H,该文件中有许多在Windows中使用的基本型态定义,而且它本身也包括WINNT.H。WINNT.H处理基本的Unicode支持。

WINNT.H的前面包含C的表头文件CTYPE.H,这是C的众多表头文件之一,包括wchar_t的定义。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定义为一般的字符类型。如果定义了标识符UNICODE(没有底线),则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
        
如果已经在某个表头文件或者其它表头文件中定义了TCHAR数据型态,那么WINNT.H和WCHAR.H表头文件都能防止其重复定义。不过,无论何时在程序中使用其它表头文件时,都应在所有其它表头文件之前包含WINDOWS.H。

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宏的方法一样,只是不必操心底线。我将在本书中使用这个宏的TEXT版本。

这些定义可使您在同一程序中混合使用ASCII和Unicode字符串,或者编写一个可被ASCII或Unicode编译的程序。如果您希望明确定义8位字符变量和字符串,请使用CHAR、PCHAR(或者其它),以及带引号的字符串。为明确地使用16位字符变量和字符串,请使用WCHAR、PWCHAR,并将L添加到引号前面。对于是8位还是16位取决于UNICODE标识符的定义的变量或字符串,要使用TCHAR、PTCHAR和TEXT宏。

Windows函数呼叫


从Windows 1.0到Windows 3.1的16位Windows中,MessageBox函数位于动态链接库USER.EXE。在Windows 3.1软件开发套件的WINDOWS.H中,MessageBox函数定义如下:

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

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

这就是Windows支持Unicode的关键:在USER32.DLL中,没有32位MessageBox函数的进入点。实际上,有两个进入点,一个名为MessageBoxA(ASCII版),另一个名为MessageBoxW(宽字符版)。用字符串作参数的每个Win32函数都在操作系统中有两个进入点!幸运的是,您通常不必关心这个问题,程序中只需使用MessageBox。与TCHAR表头文件一样,每个Windows表头文件都有我们需要的技巧。

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

WINUSERAPI int WINAPI MessageBoxA (HWND hWnd, LPCSTR lpText, 
                           LPCSTR lpCaption, UINT uType) ;
        
下面是MessageBoxW:

WINUSERAPI int WINAPI MessageBoxW (HWND hWnd, LPCWSTR lpText,
        
                           LPCWSTR lpCaption, UINT uType) ;
        
注意,MessageBoxW函数的第二个和第三个参数是指向宽字符的指针。

如果需要同时使用并分别匹配ASCII和宽字符函数呼叫,那么您可在Windows程序中明确地使用MessageBoxA和MessageBoxW函数。但大多数程序写作者将继续使用MessageBox。根据是否定义了UNICODE,MessageBox将与MessageBoxA或MessageBoxW一样。在WINUSER.H中完成这一技巧时,程序相当琐碎:

#ifdef UNICODE
        
#define MessageBox  MessageBoxW
        
#else
        
#define MessageBox  MessageBoxA
        
#endif
        
这样,如果定义了UNICODE标识符,那么程序中所有的MessageBox函数呼叫实际上就是MessageBoxW函数;否则,就是MessageBoxA函数。

执行该程序时,Windows将程序中不同的函数呼叫与不同的Windows动态链接库的进入点连结。虽然只有少数例外,但是,在Windows 98中不能执行Unicode版的Windows函数。虽然这些函数有进入点,但通常返回错误代码。应用程序注意这些返回的错误并采取一些合理的动作。

Windows的字符串函数


正如前面谈到的,Microsoft 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) ;
        
这些函数与C链接库中对应的函数功能相同。如果定义了UNICODE标识符,那么这些函数将接受宽字符串,否则只接受常规字符串。宽字符串版的lstrlenW函数可在Windows 98中执行。

在Windows中使用printf

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -