CString
因爲一個MFC CString類的對象包含TCHAR類型的字符,所以確切的字符類型取決於你所定義的預處理符號。大體來說,CString 很像STL string,這意味着你必須把它當成不透明的對象,只能使用CString提供的方法來修改CString對象。CString有一個string所不具備的優點:CString具有接收MBCS和Unicode兩種字符串的構造函數,它還有一個LPCTSTR轉換符,所以你可以把CString對象直接傳給一個接收LPCTSTR的函數而不需要調用c_str()函數。
// ConstructingCString s1 = "char string"; // construct from a LPCSTRCString s2 = L"wide char string"; // construct from a LPCWSTRCString s3 ( '' '', 100 ); // pre-allocate a 100-byte buffer, fill with spacesCString s4 = "New window text"; // You can pass a CString in place of an LPCTSTR: SetWindowText ( hwndSomeWindow, s4 ); // Or, equivalently, explicitly cast the CString: SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 );你可以從你的字符串表中裝載一個字符串,CString的一個構造函數和LoadString()函數可以完成它。Format()方法能夠從字符串表中隨意的讀取一個具有一定格式的字符串。
// Constructing/loading from string tableCString s5 ( (LPCTSTR) IDS_SOME_STR ); // load from string tableCString s6, s7; // Load from string table. s6.LoadString ( IDS_SOME_STR ); // Load printf-style format string from the string table: s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... );第一個構造函數看起來有點奇怪,但是這實際上是文檔說明的裝入一個字符串的方法。 注意,對一個CString變量,你可以使用的唯一合法轉換符是LPCTSTR。轉換成LPTSTR(非常量指針)是錯誤的。養成把一個CString變量轉換成LPTSTR的習慣將會給你帶來傷害,因爲當你的程序後來崩潰時,你可能不知道爲什麼,因爲你到處都使用同樣的代碼而那時它們都恰巧正常工作。正確的得到一個指向緩衝區的非常量指針的方法是調用GetBuffer()方法。下面是正確的用法的一個例子,這段代碼是給一個列表控件中的項設定文字:
CString str = _T("new text");LVITEM item = {0}; item.mask = LVIF_TEXT; item.iItem = 1; item.pszText = (LPTSTR)(LPCTSTR) str; // WRONG! item.pszText = str.GetBuffer(0); // correct ListView_SetItem ( &item );str.ReleaseBuffer(); // return control of the buffer to strpszText成員是一個LPTSTR變量,一個非常量指針,因此你需要對str調用GetBuffer()。GetBuffer()的參數是你需要CString爲緩衝區分配的最小長度。如果因爲某些原因,你需要一個可修改的緩衝區來存放1K TCHARs,你需要調用GetBuffer(1024)。把0作爲參數時,GetBuffer()返回的是指向字符串當前內容的指針。
上面劃線的語句可以被編譯,在這種情況下,甚至可以正常起作用。但這並不意味着這行代碼是正確的。通過使用非常量轉換,你已經破壞了面向對象的封裝,並對CString的內部實現作了某些假定。如果你有這樣的轉換習慣,你終將會陷入代碼崩潰的境地。你會想代碼爲什麼不能正常工作了,因爲你到處都使用同樣的代碼而那些代碼看起來是正確的。
你知道人們總是抱怨現在的軟件的bug是多麼的多嗎?軟件中的bug是因爲程序員寫了不正確的代碼。難道你真的想寫一些你知道是錯誤的代碼來爲所有的軟件都滿是bug這種認識做貢獻嗎?花些時間來學習使用CString的正確方法讓你的代碼在任何時間都正常工作把。
CString 有兩個函數來從一個 CString 創建一個 BSTR。它們是 AllocSysString() 和SetSysString()。
// Converting to BSTRCString s5 = "Bob!";BSTR bs1 = NULL, bs2 = NULL; bs1 = s5.AllocSysString(); s5.SetSysString ( &bs2 ); SysFreeString ( bs1 ); SysFreeString ( bs2 );COleVariant
COleVariant和CComVariant.很相似。COleVariant繼承自VARIANT,所以它可以傳給接收VARIANT的函數。然而,不像CComVariant,COleVariant只有一個LPCTSTR構造函數。沒有對LPCSTR 和LPCWSTR的構造函數。在大多數情況下這不是一個問題,因爲不管怎樣你的字符串很可能是LPCTSTRs,但這是一個需要意識到的問題。COleVariant還有一個接收CString參數的構造函數。
// ConstructingCString s1 = _T("tchar string");COleVariant v1 = _T("Bob"); // construct from an LPCTSTRCOleVariant v2 = s1; // copy from a CString像CComVariant一樣,你必須直接訪問VARIANT的成員。如果需要把VARIANT轉換成一個字符串,你應該使用ChangeType()方法。然而,COleVariant::ChangeType()如果失敗會拋出異常,而不是返回一個表示失敗的HRESULT代碼。
// Extracting dataCOleVariant v3 = ...; // fill in v3 from somewhereBSTR bs = NULL; try { v3.ChangeType ( VT_BSTR ); bs = v3.bstrVal; } catch ( COleException* e ) { // error, couldn''t convert } SysFreeString ( bs );
WTL 類
CString
WTL的CString的行爲和MFC的 CString完全一樣,所以你可以參考上面關於MFC的 CString的介紹。
CLR 和 VC 7 類
System::String是用來處理字符串的.NET類。在內部,一個String對象包含一個不可改變的字符串序列。任何對String對象的操作實際上都是返回了一個新的String對象,因爲原始的對象是不可改變的。String的一個特性是如果你有不止一個String對象包含相同的字符序列,它們實際上是指向相同的對象的。相對於C++的使用擴展是增加了一個新的字符串常量前綴S,S用來代表一個受控的字符串常量(a managed string literal)。
// ConstructingString* ms = S"This is a nice managed string";你可以傳遞一個非受控的字符串來創建一個String對象,但是樣會比使用受控字符串來創建String對象造成效率的微小損失。這是因爲所有以S作爲前綴的相同的字符串實例都代表同樣的對象,但這對非受控對象是不適用的。下面的代碼清楚地闡明瞭這一點:
String* ms1 = S"this is nice";String* ms2 = S"this is nice";String* ms3 = L"this is nice"; Console::WriteLine ( ms1 == ms2 ); // prints true Console::WriteLine ( ms1 == ms3); // prints false正確的比較可能沒有使用S前綴的字符串的方法是使用String::CompareTo()
Console::WriteLine ( ms1->CompareTo(ms2) ); Console::WriteLine ( ms1->CompareTo(ms3) );上面的兩行代碼都會打印0,0表示兩個字符串相等。 String和MFC 7 CString之間的轉換是很容易的。CString有一個向LPCTSTR的轉換操作,而String有兩個接收char* 和 wchar_t*的構造函數,因此你可以把一個CString變量直接傳給一個String的構造函數。
CString s1 ( "hello world" );String* s2 ( s1 ); // copy from a CString反方向的轉換也很類似
String* s1 = S"Three cats";CString s2 ( s1 );這也許會使你感到一點迷惑,但是它確實是起作用的。因爲從VS.NET 開始,CString 有了一個接收String 對象的構造函數。
CStringT ( System::String* pString );對於一些快速操作,你可能想訪問底層的字符串:
String* s1 = S"Three cats"; Console::WriteLine ( s1 );const __wchar_t __pin* pstr = PtrToStringChars(s1); for ( int i = 0; i < wcslen(pstr); i++ ) (*const_cast<__wchar_t*>(pstr+i))++; Console::WriteLine ( s1 );PtrToStringChars()返回一個指向底層字符串的const __wchar_t* ,我們需要固定它,否則垃圾收集器或許會在我們正在管理它的內容的時候移動了它。
在 printf-style 格式函數中使用字符串類
當你在printf()或者類似的函數中使用字符串封裝類時你必須十分小心。這些函數包括sprintf()和它的變體,還有TRACE和ATLTRACE宏。因爲這些函數沒有對添加的參數的類型檢查,你必須小心,只能傳給它們C語言風格的字符串指針,而不是一個完整的字符串類。
例如,要把一個_bstr_t 字符串傳給ATLTRACE(),你必須使用顯式轉換(LPCSTR) 或者(LPCWSTR):
_bstr_t bs = L"Bob!";ATLTRACE("The string is: %s in line %d/n", (LPCSTR) bs, nLine);
如果你忘了使用轉換符而把整個_bstr_t對象傳給了函數,將會顯示一些毫無意義的輸出,因爲_bstr_t保存的內部數據會全部被輸出。
所有類的總結
兩個字符串類之間進行轉換的常用方式是:先把源字符串轉換成一個C語言風格的字符串指針,然後把這個指針傳遞給目的類型的構造函數。下面這張表顯示了怎樣把一個字符串轉換成一個C語言風格的字符串指針以及哪些類具有接收C語言風格的字符串指針的構造函數。
Class | string type | convert to char*? | convert to const char*? | convert to wchar_t*? | convert to const wchar_t*? | convert to BSTR? | construct from char*? | construct from wchar_t*? |
_bstr_t | BSTR | yes cast1 | yes cast | yes cast1 | yes cast | yes2 | yes | yes |
_variant_t | BSTR | no | no | no | cast to _bstr_t3 |
cast to _bstr_t3 |
yes | yes |
string | MBCS | no | yes c_str() method | no | no | no | yes | no |
wstring | Unicode | no | no | no | yes c_str() method | no | no | yes |
CComBSTR | BSTR | no | no | no | yes cast to BSTR | yes cast | yes | yes |
CComVariant | BSTR | no | no | no | yes4 | yes4 | yes | yes |
CString | TCHAR | no6 | in MBCS builds, cast |
no6 | in Unicode builds, cast |
no5 | yes | yes |
COleVariant | BSTR | no | no | no | yes4 | yes4 | in MBCS builds |
in Unicode builds |