寫了n年程序,近來在字符串上栽了。:( 認(rèn)真的研究了一些關(guān)于字符串的文章,在此記下。
許多關(guān)于字符串的問題,在文章最后的參考文章中,相信有更加深入和精確的描述。不過關(guā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編譯,得到的結(jié)果都是一樣的。因為string (實際上是basic_string)不會自動進行從MBCS到UNICODE的轉(zhuǎn)換。所以使用printf或者wprintf輸出即可。前提當(dāng)然是你的系統(tǒng)需要支持中文。
讓我們把代碼修改一下,希望可以輸出字符串的內(nèi)容:
{
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()最后調(diào)用的函數(shù),該函數(shù)向VisualStudio的Output窗口輸出,而printf和wprintf向
console窗口輸出。最后的結(jié)果如何?OutputDebugStringW輸出的是怪字符??!WHY??
s1.c_str()傳遞給OutputDebugStringW和wprintf不是都是內(nèi)容相同的LPCWSTR嗎?
1)因為OutputDebugStringW的字符串必須是真正UNICODE編碼的字符串,而不是所有的const wchar_t*(即LPCWSTR)都可以得到正確結(jié)果。在這里s1雖然使用wchar_t類型,但是實際的內(nèi)容卻是MBCS編碼。
2)反之亦然:CRT的wprintf只支持MBCS編碼的字符串,而不能是UNICODE編碼的字符串。在程序段2我們可以看到真正的UNICODE編碼的字符串。
這是我多年來的一個誤區(qū):wchar_t類型的字符串就是UNICODE字符串。實際應(yīng)該理解為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的結(jié)果都是一樣的
2)Line 15.?????
CString s1 (L" 蔡 "); // b2 00 cc 00 00 00
得到的還是MBCS的字符串,只是增加了0作為trail byte。而不是我理解的UNICODE字符串!這是我多年來的另外一個誤區(qū):_T在_UNICODE下轉(zhuǎn)換為L,而L后面的字符串是UNICODE編碼。在參考資料MSDN的“TCHAR.H 中的一般文本映射”中(以及MSDN的許多地方),可以看到類似的說明:
一般文本數(shù)據(jù)類型映射
一般文本數(shù)據(jù)類型名 | 未定義 _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 | 無效(由預(yù)處理器移除) | 無效(由預(yù)處理器移除) | L (將后面的字符或字符串轉(zhuǎn)換成相應(yīng)的 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定義而得)的構(gòu)造函數(shù),在這里實際上是CStringW 的構(gòu)造函數(shù),根據(jù)輸入的參數(shù)是char類型字符串,會自動調(diào)用MultiByteToWideChar轉(zhuǎn)換MBCS字符串為UNICODE字符串。
4)相應(yīng)Line 12,那么Line 35得到的結(jié)果32 a8 ac 00是什么?和Line 12類似:
CString構(gòu)造函數(shù),在這里實際上是CStringA的構(gòu)造函數(shù),根據(jù)輸入的參數(shù)是wchar_t類型字符串,會自動調(diào)用WideCharToMultiByte轉(zhuǎn)換UNICODE字符串為MBCS字符串。但是根據(jù)2),我們知道,輸入的參數(shù)不是UNICODE字符串,只是MBCS的wchar_t類型字符串,所以得到的是錯誤的編碼。
總結(jié)以上,可知:
1)CRT不能生成和處理UNICODE類型字符串,對于wchar_t類型字符串,只能處理MBCS編碼;
2)VC RunTime中帶W后綴的函數(shù),和所有的COM函數(shù),對于wchar_t類型字符串,只能處理UNICODE編碼;
3)如果不考慮輸出,只是進行拷貝、比較等操作,只要注意_T的含義和字符串的字符類型長度就可以了;但是如果需要輸出,必須注意字符串的編碼轉(zhuǎn)換。
4)
使用UNICODE或者MBCS的編譯選項,只是影響字符串的字符類型(自動識別_T,CString等,自動把函數(shù)轉(zhuǎn)換為xxxxA()或者xxxxW
()),不影響字符串的編碼。下表的代碼結(jié)果不受編譯選項影響(這也是編譯器處理_T,CString等后的結(jié)果):
CStringA s = "xxx"; // 等于 CA2A("xxx") 結(jié)果為SBCS(單字節(jié)編碼) printf()正確 OutputDebugStringA()正確 |
CStringW s = "xxx"; // 等于 CA2W("xxx") 結(jié)果為UNICODE編碼 wprintf()錯誤 OutputDebugStringW()正確 |
CStringA s = L"xxx"; // 等于 CW2A(L"xxx") 結(jié)果為MBCS編碼(可能錯誤) printf()錯誤 OutputDebugStringA()錯誤 |
CStringW s = L"xxx"; // 等于 CW2W("xxx") 不改變字符串的編碼,仍然是MBCS。 wprintf()正確 OutputDebugStringW()錯誤 |
疑問:我認(rèn)為對于CW2W是由系統(tǒng)編碼決定,可以直接得到UNICODE嗎?
關(guān)于CW2A,如果后面的字符串的確是UNICODE編碼,則可以得到正確的相應(yīng)MBCS編碼字符串。實際上,這也是我們要輸出UNICODE的方法:
CStringW s = "蔡"; // s 現(xiàn)在是UNICODE編碼
// wprintf(s)不正確
CW2A psz(s); // psz現(xiàn)在是s相應(yīng)的正確的MBCS編碼!
printf(psz); // 正確
// All is OK, a little more to say
CA2W wsz(psz); // wsz現(xiàn)在是psz的錯誤的UNICODE編碼,即32 a8 ac 00
推薦參考資料
- The Complete Guide to C++ Strings:個人認(rèn)為很好和很全面的文章
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 -
當(dāng)然,少不了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