寫了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