寫了n年程序,近來在字符串上栽了。:( 認真的研究了一些關于字符串的文章,在此記下。

許多關于字符串的問題,在文章最后的參考文章中,相信有更加深入和精確的描述。不過關于中文的處理,我想先補充一些自己的看法。

背景:WIN32 console程序,使用printf輸出字符串。相信許多人都有使用過。

平臺:VisualStudio.NET 2003(MFC 7.1)。


MBCS UNICODE
b2 cc 21 85
A 41 00 41 00

程序段1:使用std::string

#include <string>


{

// NOTE: use s1._Bx._Buf to see the memory

std::string s1 ("蔡"); // b2 cc 00

}

{

std::wstring s1(L"蔡"); // b2 00 cc 00 00 00

}

以上代碼,不管使用MBCS還是_UNICODE編譯,得到的結果都是一樣的。因為string (實際上是basic_string)不會自動進行從MBCS到UNICODE的轉換。所以使用printf或者wprintf輸出即可。前提當然是你的系統需要支持中文。


讓我們把代碼修改一下,希望可以輸出字符串的內容:

{
std::string s1 ("蔡"); // b2 cc 00
OutputDebugStringA(s1.c_str());
printf(s1.c_str());
}

{

std::wstring s1(L"蔡"); // b2 00 cc 00 00 00
OutputDebugStringW(s1.c_str());
wprintf(s1.c_str());
}

OutputDebugString 實際上就是ATLTRACE()最后調用的函數,該函數向VisualStudio的Output窗口輸出,而printf和wprintf向 console窗口輸出。最后的結果如何?OutputDebugStringW輸出的是怪字符!!WHY?? s1.c_str()傳遞給OutputDebugStringW和wprintf不是都是內容相同的LPCWSTR嗎?

1)因為OutputDebugStringW的字符串必須是真正UNICODE編碼的字符串,而不是所有的const wchar_t*(即LPCWSTR)都可以得到正確結果。在這里s1雖然使用wchar_t類型,但是實際的內容卻是MBCS編碼。

2)反之亦然:CRT的wprintf只支持MBCS編碼的字符串,而不能是UNICODE編碼的字符串。在程序段2我們可以看到真正的UNICODE編碼的字符串

這是我多年來的一個誤區:wchar_t類型的字符串就是UNICODE字符串。實際應該理解為UNICODE是16位的字符集,可以使用wchar_t類型進行存儲。



程序段2:使用CString
請看程序后面的說明。

1.???????? ////////////////// START: compile with _UNICODE ///////////////////

2.???????? {

3.???????? CString s1 ("A"); // 41 00 00 00

4.???????? }

5.???????? {

6.???????? CString s1 (L"A"); // 41 00 00 00

7.???????? }

8.???????? {

9.???????? CString s1 (_T("A")); // 41 00 00 00

10.???? }

11.???? {

12.???? CString s1 (" "); // 21 85 00 00

13.???? }

14.???? {

15.???? CString s1 (L" "); // b2 00 cc 00 00 00

16.???? }

17.???? {

18.???? CString s1 (_T(" ")); // b2 00 cc 00 00 00

19.???? }

20.???? ////////////////// END: compile with _UNICODE ///////////////////

21.???? ////////////////// START: compile with _MBCS ///////////////////

22.???? {

23.???? CString s1 ("A"); // 41 00

24.???? }

25.???? {

26.???? CString s1 (L"A"); // 41 00

27.???? }

28.???? {

29.???? CString s1 (_T("A")); // 41 00

30.???? }

31.???? {

32.???? CString s1 (" "); // b2 cc 00

33.???? }

34.???? {

35.???? CString s1 (L" "); // 32 a8 ac 00

36.???? }

37.???? {

38.???? CString s1 (_T(" ")); // b2 cc 00

39.???? }

40.???? ////////////////// END: compile with _MBCS ///////////////////


1)對于英文字母‘A’,MBCS和UNICODE的結果都是一樣的

2)Line 15.?????

CString s1 (L" "); // b2 00 cc 00 00 00

得到的還是MBCS的字符串,只是增加了0作為trail byte。而不是我理解的UNICODE字符串!這是我多年來的另外一個誤區:_T在_UNICODE下轉換為L,而L后面的字符串是UNICODE編碼。在參考資料MSDN的“TCHAR.H 中的一般文本映射”中(以及MSDN的許多地方),可以看到類似的說明:

一般文本數據類型映射

一般文本數據類型名 未定義 _UNICODE 或 _MBCS 已定義
_MBCS
已定義 _UNICODE
_TCHAR char char wchar_t
_TINT int int wint_t
_TSCHAR signed char signed char wchar_t
_TUCHAR unsigned char unsigned char wchar_t
_TXCHAR char unsigned char wchar_t
_T_TEXT 無效(由預處理器移除) 無效(由預處理器移除) L (將后面的字符或字符串轉換成相應的 Unicode 形式)

實際上L"xxx"只是通知編譯器,我們需要的是wchar_t類型的字符串,而不能影響編碼。

真正的UNICODE字符串在哪里?

3)Line 12:

等同于??? CStringW s1 (""); // 21 85 00 00

我們看到,得到了真正的UNICODE 字符串。因為CString(在MFC 7.1中,不存在MFC的CString,實際上由ATL::CStringT通過typedef定義而得)的構造函數,在這里實際上是CStringW 的構造函數,根據輸入的參數是char類型字符串,會自動調用MultiByteToWideChar轉換MBCS字符串為UNICODE字符串。

4)相應Line 12,那么Line 35得到的結果32 a8 ac 00是什么?和Line 12類似:

CString構造函數,在這里實際上是CStringA的構造函數,根據輸入的參數是wchar_t類型字符串,會自動調用WideCharToMultiByte轉換UNICODE字符串為MBCS字符串。但是根據2),我們知道,輸入的參數不是UNICODE字符串,只是MBCS的wchar_t類型字符串,所以得到的是錯誤的編碼。

 

總結以上,可知:

1)CRT不能生成和處理UNICODE類型字符串,對于wchar_t類型字符串,只能處理MBCS編碼;

2)VC RunTime中帶W后綴的函數,和所有的COM函數,對于wchar_t類型字符串,只能處理UNICODE編碼;

3)如果不考慮輸出,只是進行拷貝、比較等操作,只要注意_T的含義和字符串的字符類型長度就可以了;但是如果需要輸出,必須注意字符串的編碼轉換。
4) 使用UNICODE或者MBCS的編譯選項,只是影響字符串的字符類型(自動識別_T,CString等,自動把函數轉換為xxxxA()或者xxxxW ()),不影響字符串的編碼。下表的代碼結果不受編譯選項影響(這也是編譯器處理_T,CString等后的結果):

CStringA s = "xxx"; // 等于 CA2A("xxx")
結果為SBCS(單字節編碼)
printf()正確
OutputDebugStringA()正確
CStringW s = "xxx"; // 等于 CA2W("xxx")
結果為UNICODE編碼
wprintf()錯誤
OutputDebugStringW()正確
CStringA s = L"xxx"; // 等于 CW2A(L"xxx")
結果為MBCS編碼(可能錯誤)
printf()錯誤
OutputDebugStringA()錯誤
CStringW s = L"xxx"; // 等于 CW2W("xxx")
不改變字符串的編碼,仍然是MBCS
wprintf()正確
OutputDebugStringW()錯誤

疑問:我認為對于CW2W是由系統編碼決定,可以直接得到UNICODE嗎?

關于CW2A,如果后面的字符串的確是UNICODE編碼,則可以得到正確的相應MBCS編碼字符串。實際上,這也是我們要輸出UNICODE的方法:

CStringW s = "蔡"; // s 現在是UNICODE編碼

// wprintf(s)不正確

CW2A psz(s); // psz現在是s相應的正確的MBCS編碼!

printf(psz); // 正確

// All is OK, a little more to say

CA2W wsz(psz); // wsz現在是psz的錯誤的UNICODE編碼,即32 a8 ac 00

 

 

推薦參考資料

  • The Complete Guide to C++ Strings:個人認為很好和很全面的文章

The Complete Guide to C++ Strings, Part I - Win32 Character Encodings

http://www.codeproject.com/string/CPPStringGuide1.asp


The Complete Guide to C++ Strings, Part I - Win32 Character Encodings

http://www.codeproject.com/string/cppstringguide2.asp

  • 這2篇是不錯的中文翻譯。 :)


    C++字符串完全指引之一 —— Win32 字符編碼
    http://www.vckbase.com/document/viewdoc/?id=1082

    C++字符串完全指引之二 —— 字符串封裝類

    http://www.vckbase.com/document/viewdoc/?id=1096


  • 其它一篇
    STL 字符串類與 UNICODE

    http://www.vckbase.com/vckbase/default.aspx

  • 當然,少不了MSDN

    TCHAR.H 中的一般文本映射

    http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/vccore/html/_core_generic.2d.text_mappings_in_tchar..h.asp

    建議:最好把整個“國際編程”目錄看一次(雖然看完還是糊涂 :) )

    http://msdn.microsoft.com/library/CHS/vccore/html/_core_International_Programming_Topics.asp