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

MFC類

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 str      
  pszText成員是一個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
  • 1、即使 _bstr_t 提供了向非常量指針的轉換操作符,修改底層的緩衝區也會已引起GPF如果你溢出了緩衝區或者造成內存泄漏。 
  • 2、_bstr_t 在內部用一個 wchar_t* 來保存 BSTR,所以你可以使用 const wchar_t* 來訪問BSTR。這是一個實現細節,你可以小心的使用它,將來這個細節也許會改變。 
  • 3、如果數據不能轉換成BSTR會拋出一個異常。 
  • 4、使用 ChangeType(),然後訪問 VARIANT 的 bstrVal 成員。在MFC中,如果數據轉換不成功將會拋出異常。 
  • 5、這裏沒有轉換 BSTR 函數,然而 AllocSysString() 返回一個新的BSTR。 
  • 6、使用 GetBuffer() 方法,你可以暫時地得到一個非常量的TCHAR指針。 
  •   

    發佈了254 篇原創文章 · 獲贊 6 · 訪問量 39萬+
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章