2.8.1 C 运行期库对Unicode的支持
2.8.1.1 对标准的C头文件<String.h> 作了修改,定义了一个名字为wchar_t的数据类型,用于处理Unicode 字符。
typedef unsigned short wchar_t;
2.8.1.2 添加了与ANSI C字符串对应的字符串处理函数,以wcs开头用于处理Unicode字符。
wchar_t * wcscat(wchar_t *,const wchar_t *);
wchar_t * wcschr(const wchar_t *,wchar_t);
int wcscmp(const wchar_t *,const wchar_t *);
wchar_t * wcscpy(wchar_t *,const wchar_t *);
size_t wcslen(const wchar_t *); 2.8.1.3 为了建立双重功能,提供了<TChar.h>帮助创建ANSI/Unicode通用源代码文件,<TChar.h>提供了一些通用宏,根据是否定义了_UNICODE来判断是使用ANSI数据类型还是Unicode数据类型,是使用ANSI字符串处理函数还是使用Unicode字符串处理函数。
类似于"Error"这样的字符串提供了 _T 和 _TEXT 2个通用宏。 #ifdef _UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR; //!UNICODE
#ifdef _UNICODE
#define __T(x) L##x
#else
#define __T(x) x //!UNICODE
#ifdef _UNICODE
#define _TEXT(x) L ## x
#else
#define _TEXT(x) x //!UNICODE
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen //!UNICODE
2.8.1.4 printf函数家族是要介绍的最后一组C运行期函数。如果在定义了_UNICODE的情况下编译你的源代码模块,那么printf函数家族便希望所有字符和字符串参数代表Unicode字符和字符串。但是,如果在没有定义_UNICODE的情况下编译你的源代码模块,printf函数家族便希望传递给它的所有字符和字符串都是ANSI字符和字符串。但有个坏消息:在Windows程序中不能使用printf。虽然Windows程序中可以使用大多数C的执行时期链接库-实际上,许多程序写作者更愿意使用C内存管理和文件I/O函数而不是Windows中等效的函数-Windows对标准输入和标准输出没有概念。在Windows程序中可使用fprintf,而不是printf。还有一个好消息,那就是仍然可以使用sprintf及sprintf系列中的其它函数来显示文字。这些函数除了将内容格式化输出到函数第一个参数所提供的字符串缓冲区以外,其功能与printf相同。
2.8.3 Windows中的Unicode函数和ANSI函数
2.8.3.1 Windows NT从底层支援Unicode。这意味着Windows
NT内部使用由16位字符组成的字符串。因为世界上其它许多地方还不使用16位字符串,所以Windows NT必须经常将字符串在操作系统内转换。Windows
NT可执行为ASCII、Unicode或者ASCII和Unicode混合编写的程序。即,Windows
NT支持不同的API函数呼叫,这些函数接受8位或16位的字符串. 2.8.3.2 一个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版本。
2.8.3.3 Windows头文件也定义了ANSI/Unicode通用数据类型PTSTR和PCTSTR.这些数据类型既可以指ANSI字符串,也可以指Unicode字符串,这取决于当编译程序模块时是否定义了UNICODE宏。这里的UNICODE宏没有前置的下划线。_UNICODE宏用于C运行期头文件。
2.8.4 Windows字符串函数
2.8.4.1 Windows提供了一组对字符串进行操作的通用函数,这些函数是通过宏来实现的,这些宏既可以调用函数的Unicode版本,也可以调用函数的ANSI版本,这要根据编译源代码模块时是否已经定义了UNICODE而定。例如,如果没有定义UNICODE,lstrcat函数将扩展为lstrcatA。如果定义了UNICODE,lstrcat将扩展为lstrcatW
lstrcat 将一个字符串置于另一个字符串的结尾处
lstrcmp 对两个字符串进行区分大小写的比较 对Windows函数CompareString的调用来实现的。 lstrcmpi 对两个字符串进行不区分大小写的比较 对Windows函数CompareString的调用来实现的。 lstrcpy 将一个字符串拷贝到内存中的另一个位置
lstrlen 返回字符串的长度(按字符数来计量)
2.8.4.2 Windows还提供了<ShlWApi.h>头文件包含一组范围很广的通用字符串操作函数,类似于StrCat、StrChr、StrCmp和StrCpy等.这些字符串函数既有ANSI版本,也有Unicode版本,例如StrCatA和StrCatW,这取决于当编译程序模块时是否定义了UNICODE宏,这些函数与C运行期字符串函数(如strcpy和wcscpy很相似),但是该操作系统函数是操作系统的一个组成部分,操作系统的许多组件都使用这些函数,而不使用C运行期库。建议最好使用操作系统函数,而不要使用C运行期字符串函数。这将有助于稍稍提高你的应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe所使用。由于这些函数使用得很多,因此,在你的应用程序运行时,它们可能已经被装入RAM。
2.9.4 在Unicode与ANSI之间转换字符串
2.9.4.1 <
待续>
2.9.5 成为符合ANSI和Unicode的应用程序即使你不打算立即使用Unicode,最好也应该着手将你的应用程序转换成符合Unicode 的应用程序。下面是应该遵循的一些基本原则:
• 将文本串视为字符数组,而不是chars 数组或字节数组。
• 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串。
• 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。
• 将TEXT宏用于原义字符和字符串。
• 执行全局性替换(例如用PTSTR替换PSTR)。
• 修改字符串运算问题。例如函数通常希望你在字符中传递一个缓存的大小,而不是字节。
这意味着你不应该传递sizeof(szBuffer),而应该传递(sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那么请记住要按字节来分配内存。这就是说,应该调用malloc(nCharacters
*sizeof(TCHAR)),而不是调用malloc(nCharacters)。在上面所说的所有原则中,这是最难记住的一条原则,如果操作错误,编译器将不发出任何警告。
<Windows核心编程>
<Windows程序设计>2.9.5 C++ string
我经常在 C++ 程序中使用标准模板库(STL)的 std::string 类,但在
使用 Unicode 时碰到了问题。在使用常规 C 风格的字符串时,我可以使用 TCHAR 和 _T 宏,这样针对 Unicode 或 ASCII 均可以进行编译,但我
总是发现这种ASCII/Unicode的结合很难与 STL 的 string 类一起使用。你有什么好的建议吗?
是的,一旦知道 TCHAR 和_T 是如何工作的,那么这个问题很简单。基本思想是 TCHAR 要么是char,要么是 wchar_t,这取决于 _UNICODE 的值:
// abridged from tchar.h
#ifdef _UNICODE
typedef wchar_t TCHAR;
#define __T(x) L ## x
#else
typedef char TCHAR;
#define __T(x) x
#endif
当你在工程设置中选择 Unicode 字符集时,编译器会用 _UNICODE 定义进行编译。如果你选择MBCS(多字节字符集),则编译器将不会带 _UNICODE 定义
。一切取决于_UNICODE 的值。同样,每一个使用字符指针的 Windows API 函数会有一个 A(ASCII) 和一个 W(Wide/Unicode) 版本,这些版本的
实际定义也是根据 _UNICODE 的值来决定:
#ifdef UNICODE
#define CreateFile CreateFileW
#else
#define CreateFile CreateFileA
#endif
同样,_tprintf 和 _tscanf 对应于 printf 和 scanf。所有带"t"的版本使用 TCHARs
取代了chars。那么怎样把以上的这些应用到 std::string 上呢?很简单。STL已经有一个使用宽字符定义的wstring类
(在 xstring 头文件中定义)。string 和 wstring 均是使用 typedef 定义的模板类,基于
basic_string,
用它可以创建任何字符类型的字符串类。以下就是 STL 定义的 string 和 wstring:
// (from include/xstring)
typedef basic_string< char,
char_traits< char >, allocator< char > >
string;
typedef basic_string< wchar_t,
char_traits< wchar_t >, allocator< wchar_t > >
wstring;
模板被潜在的字符类型(char 或 wchar_t)参数化,因此,对于 TCHAR 版本,所要做的就是使用 TCHAR 来模仿定义。
typedef basic_string< TCHAR,
char_traits< TCHAR >,
allocator< TCHAR > >
tstring;
现在便有了一个 tstring,它基于 TCHAR——也就是说,它要么是 char,要么是 wchar_t,这取决于 _UNICODE 的值。
以上示范并指出了 STL 是怎样使用 basic_string 来实现基于任何类型的字符串的。定义一个新的 typedef 并不是解决此问题最有效的方法。一个更好的方法是基于 string 和wstring 来简单
地定义 tstring,如下:
#ifdef _UNICODE
#define tstring wstring
#else
#define tstring string
#endif
这个方法之所以更好,是因为 STL 中已经定义了 string 和 wstring,那为什么还要使用模板来定义一个新的和其中之一一样的字符串类呢?
暂且叫它 tstring。可以用 #define 将 tstring 定义为 string 和 wstring,这样可以避免创建另外一个模板类(
虽然当今的编译器非常智能,如果它把该副本类丢弃,我一点也不奇怪)。[编辑更新-2004/07/30:typedef
不创建新类,只是为某个类型引入限定范围的名称,typedef 决不会定义一个新的类型]。不管怎样,一旦定义了 tstring,便可以像下面这样编码:
tstring s = _T("Hello, world");
_tprintf(_T("s =%s\n"), s.c_str());
basic_string::c_str 方法返回一个指向潜在字符类型的常量指针;在这里,该字符类型要么是const char*,要么是 const wchar_t*。
Figure 2 是一个简单的示范程序,举例说明了 tstring 的用法。它将“Hello,world”写入一个文件,并报告写了多少个字节。我对
工程进行了设置,以便用 Unicode 生成 debug 版本,用 MBCS 生成 Release 版本。你可以分别进行编译/生成并运行程序,然后比较结果。Figure
3 显示了例子的运行情况。
Figure 3 运行中的 tstring
顺便说一下,MFC 和 ATL 现在已经联姻,以便都使用相同的字符串实现。结合后的实现使用一个叫做 CStringT 的模板类,这在某种意义上
,其机制类似 STL 的 basic_string,用它可以根据任何潜在的字符类型来创建 CString 类。在 MFC 包含文件 afxstr.h 中定义了三种字符
串类型,如下:
typedef ATL::CStringT< wchar_t,
StrTraitMFC< wchar_t > > CStringW;
typedef ATL::CStringT< char,
StrTraitMFC< char > > CStringA;
typedef ATL::CStringT< TCHAR,
StrTraitMFC< TCHAR > > CString;
CStringW,CStringA 和 CString 正是你所期望的:CString 的宽字符,ASCII 和 TCHAR 版本。
那么,哪一个更好,STL 还是 CStirng?两者都很好,你可以选择你最喜欢的一个。但有一个问题要考虑到:就是你想要链接哪个库,以及你是否已经在使用 MFC/ATL。从编码
的角度来看,我更喜欢 CString 的两个特性:
其一是无论使用宽字符还是char,都可以很方便地对 CString 进行初始化。
CString s1 = "foo";
CString s2 = _T("bar");
这两个初始化都正常工作,因为 CString 自己进行了所有必要的转换。使用 STL 字符串,你必须使用_T()对 tstring 进行初始化,因为你
无法通过一个char*初始化一个wstring,反之亦然。
其二是 CString 对 LPCTSTR 的自动转换操作,你可以像下面这样编码:
CString s;
LPCTSTR lpsz = s;
另一方面,使用 STL 必须显式调用 c_str 来完成这种转换。这确实有点挑剔,某些人会争辩说,这样能更好地了解何时进行转换。比如,
在C风格可变参数的函数中使用 CString 可能会有麻烦,像 printf:
printf("s=%s\n", s); // 错误
printf("s=%s\n", (LPCTSTR)s); // 必需的
没有强制类型转换的话,得到的是一些垃圾结果,因为 printf 希望 s 是 char*。我敢肯定很多读者都犯过这种错误。防止这种灾祸是 STL
设计者不提供转换操作符的一个毋庸置疑的理由。而是坚持要你调用 c_str。一般来讲,喜欢使用 STL 家伙趋向于理论和学究气,而
Redmontonians(译者:指微软)的大佬们则更注重实用和散漫。嘿,不管怎样,std::string 和 CString 之间的实用差别是微不足道的。
本文由 VCKBASE MTT 翻译,http://www.vckbase.com/document/viewdoc/?id=1293