?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