C 風格字符串易出錯且難管理,甚至成緩沖區溢出攻擊目標,故衍生出眾多封裝類。但面對 Win32 API、MFC、STL、WTL、VC 運行庫中的各類字符串類,開發者常不知該用哪個,也不知如何互轉。本文系統介紹所有主流字符串類的用法、構造與轉換方法。
鐵律 #1: 除非文檔明確支持,絕不使用 cast 進行字符串類型轉換。cast 不執行任何實際轉換,僅關閉編譯器類型檢查,運行時極易崩潰。
| 類型 | 含義 |
|---|---|
| WCHAR | Unicode 字符 (wchar_t) |
| TCHAR | MBCS 或 Unicode,視預處理器設置 |
| LPSTR | char* |
| LPCSTR | const char* |
| LPWSTR | WCHAR* |
| LPCWSTR | const WCHAR* |
| LPTSTR | TCHAR* |
| LPCTSTR | const TCHAR* |
OLE 相關:OLECHAR 通常為 wchar_t,LPOLESTR/LPCOLESTR 對應指針。宏 _T(x)、OLESTR(x) 用於字面量跨編碼編譯。
BSTR:Pascal 風格(長度前綴)+ C 風格(雙零結尾)混合體。指向數據首字符的指針,長度存於前 4 字節。分配/釋放用 SysAllocString/SysFreeString。
VARIANT:無類型語言交互容器,含字符串時存為 BSTR。
隱藏底層 BSTR,提供豐富構造/轉換操作符。無訪問 BSTR* 的操作符,不可作輸出參數傳給 COM 方法(需用 CComBSTR)。但滿足三條件時可隱式轉 BSTR:有 wchar_t* 轉換函數、BSTR 定義等同 wchar_t*、內部按 BSTR 格式存儲。
// 構造
_bstr_t bs1 = "char string"; // from LPCSTR
_bstr_t bs2 = L"wide string"; // from LPCWSTR
_bstr_t bs3 = bs1; // copy
_variant_t v = "Bob"; _bstr_t bs4 = v; // from _variant_t
// 提取
LPCSTR psz1 = bs1; // 自動轉 MBCS
LPCWSTR pwsz1 = bs1; // 內部 Unicode
BSTR bstr = bs1.copy(); // 複製為 BSTR,需手動 SysFreeString
注意:提供 char*/wchar_t* 非 const 轉換操作符,但絕不可修改緩衝區,否則破壞 BSTR 結構。
提供大量構造/轉換函數。字符串相關:
_variant_t v1 = "char str"; _variant_t v2 = L"wide str";
_bstr_t bs1 = "Bob"; _variant_t v3 = bs1;
// 提取
_bstr_t bs2 = v1; _bstr_t bs3 = (_bstr_t)v1; // cast 合法
// 注意:轉換失敗拋 _com_error 異常;無直接轉 MBCS,需中轉 _bstr_t 或用 ATL 宏。
可直接傳給接收 VARIANT 的 COM 方法(繼承自 VARIANT)。
管理以零結尾的字符數組,視為不透明對象,寫操作必須用成員函數。兩大特化:string (char)、wstring (wchar_t)。無內置 TCHAR 版,可自定:typedef basic_string
string str = "char"; wstring wstr = L"wide"; tstring tstr = _T("TCHAR");
// 提取:只讀指針
LPCSTR psz = str.c_str(); LPCWSTR pwsz = wstr.c_str(); LPCTSTR ptsz = tstr.c_str();
無跨字符集直接轉換,但可將 c_str() 傳給其他類構造函數:_bstr_t bs1 = str.c_str();
亮點:提供 operator BSTR() 與 operator &()(返回 BSTR*),可直接傳給 COM 方法的 BSTR 與 BSTR* 參數。自動管理內存。
CComBSTR bs1; CComBSTR bs2 = "new text";
pStuff->GetText(&bs1); // 取址傳 BSTR*
pStuff->SetText(bs2); // 隱式轉 BSTR
// 構造/提取
CComBSTR bs1 = "char"; CComBSTR bs2 = L"wide";
BSTR bstr1 = bs1; // 內部 BSTR,勿修改
BSTR bstr3 = bs1.Copy(); // 複製,需 SysFreeString
BSTR bstr4 = bs1.Detach(); // 脫管,需手動釋放
無內置轉 MBCS,需用 ATL 轉換宏。operator & 導致不能直接放入 STL 容器,解決方案:std::list。
CComVariant v1 = "char"; CComVariant v2 = L"wide";
CComBSTR bs1 = "BSTR bob"; CComVariant v3 = (BSTR)bs1;
// 提取:直接訪問 bstrVal
CComBSTR bs2 = v1.bstrVal;
// 轉型需用 ChangeType()
CComVariant v4 = ...; CComBSTR bs3;
if (SUCCEEDED(v4.ChangeType(VT_BSTR))) bs3 = v4.bstrVal;
同無直接轉 MBCS。
命名模式:[源]2[目] 或 [源]2C[目](C 表常量)。簡稱:A=MBCS、W=Unicode、T=TCHAR、OLE=OLECHAR、BSTR=目標類型。
使用步驟:#include ,函數開頭放 USES_CONVERSION。
目標非 BSTR 時結果在棧上,需拷貝延長壽命;目標為 BSTR 時必須賦值給變量/封裝類防洩漏。
void Foo(LPCWSTR); void Bar(BSTR); void Baz(BSTR*);
#include
main() {
USES_CONVERSION;
LPCSTR psz = "Bob"; string str = "Bob";
Foo(A2CW(psz)); Foo(A2CW(str.c_str())); // MBCS → Unicode
BSTR bs1 = A2BSTR(psz); CComBSTR bs2; bs2.Attach(W2BSTR(L"Bob")); // → BSTR
Bar(bs1); Bar(bs2); SysFreeString(bs1);
BSTR bs3; Baz(&bs3); string str2 = W2CA(bs3); SysFreeString(bs3); // BSTR → MBCS
}
基於 TCHAR,行為類似 STL string(不透明對象)。優勢:同時接受 MBCS/Unicode 構造,有 LPCTSTR 轉換符,可直接傳給 API。唯一合法 cast 為 LPCTSTR,cast 為 LPTSTR 破壞封裝,必須用 GetBuffer()/ReleaseBuffer() 獲取可修改緩衝區。
CString s1 = "char"; CString s2 = L"wide";
SetWindowText(hwnd, s2); // 直接傳 LPCTSTR
// 從字符串表加載
CString s5((LPCTSTR)IDS_STR); // 文檔規定寫法
CString s6; s6.LoadString(IDS_STR);
CString s7; s7.Format(IDS_FMT, "bob", n);
// 正確獲取可修改緩衝區
LVITEM item = {0}; item.pszText = str.GetBuffer(0); // 而非 (LPTSTR)(LPCTSTR)str
ListView_SetItem(&item); str.ReleaseBuffer();
// 轉 BSTR
BSTR bs1 = s5.AllocSysString(); s5.SetSysString(&bs2);
繼承 VARIANT,僅有 LPCTSTR 和 CString 構造函數。轉換用 ChangeType(),失敗拋 COleException。
.NET 受控字符串,內部不可變。操作返回新對象。相同字面量共享實例。字面量前綴 S 表示受控字面量。
與 MFC7 CString 互轉簡單:CString s1("hello"); String* s2(s1); 及反向。
訪問底層:PtrToStringChars(s1) 返回 pinned 指針,防 GC 移動。
printf/sprintf/TRACE/ATLTRACE 無類型檢查,必須傳 C 風格指針而非類對象。例:ATLTRACE("str: %s", (LPCSTR)bs); 忘記 cast 會輸出內部數據亂碼。
| 類 | 內部類型 | 到 char* | 到 const char* | 到 wchar_t* | 到 const wchar_t* | 到 BSTR | 從 char* 構造 | 從 wchar_t* 構造 |
|---|---|---|---|---|---|---|---|---|
| _bstr_t | BSTR | cast¹ | cast | cast¹ | cast | cast² | yes | yes |
| _variant_t | BSTR | no | no | no | cast→_bstr_t³ | cast→_bstr_t³ | yes | yes |
| string | MBCS | no | c_str() | no | no | no | yes | no |
| wstring | Unicode | no | no | no | c_str() | no | no | yes |
| CComBSTR | BSTR | no | no | no | cast | cast | yes | yes |
| CComVariant | BSTR | no | no | no | ChangeType⁴ | ChangeType⁴ | yes | yes |
| CString | TCHAR | GetBuffer⁶ | cast(MBCS)/GetBuffer(Unicode) | cast(Unicode)/GetBuffer(MBCS) | cast | AllocSysString⁵ | yes | yes |
| COleVariant | BSTR | no | no | no | ChangeType⁴ | ChangeType⁴ | MBCS build | Unicode build |
¹ 即使提供非 const cast,修改緩衝區會導致 GPF/洩漏。² 內部用 wchar_t* 存 BSTR,實現細節謹慎用。³ 失敗拋異常。⁴ 需 ChangeType 後訪問 bstrVal。⁵ 無直接轉 BSTR 函數,AllocSysString 返回新 BSTR。⁶ 用 GetBuffer 臨時獲取非 const TCHAR*。