1. 程式人生 > >TCHAR 與 STD::string 之間的若干問題 [轉]

TCHAR 與 STD::string 之間的若干問題 [轉]


我經常在 C++ 程式中使用標準模板庫(STL)的 std::string 類,但在 使用 Unicode 時碰到了問題。在使用常規 C 風格的字串時,我可以使用 TCHAR 和 _T 巨集,這樣針對 Unicode 或 ASCII 均可以進行編譯,但我 總是發現這種ASCII/Unicode的結合很難與 STL 的 string 類一起使用。你有什麼好的建議嗎?

Naren J.

是的,一旦知道 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 之間的實用差別是微不足道的。