C++字符串完全指引之二 —— 字符串封裝類(lèi)
![]() 因為C語(yǔ)言風(fēng)格的字符串容易出錯且不易管理,黑客們甚至利用可能存在的緩沖區溢出bug把C語(yǔ)言風(fēng)格的字符串作為攻擊目標,所以出現了很多字符串封裝類(lèi)。不幸的是,在某些場(chǎng)合下我們不知道該使用哪個(gè)字符串類(lèi),也不知道怎樣把一個(gè)C風(fēng)格的字符串轉換成一個(gè)字符串封裝類(lèi)。 這篇文章將介紹所有在Win32 API, MFC, STL, WTL 和 Visual C++ 運行庫中出現的字符串類(lèi)型。我將描述每一個(gè)類(lèi)的用法,告訴大家怎樣創(chuàng )建每一個(gè)類(lèi)的對象以及怎樣把一個(gè)類(lèi)轉換成其他類(lèi)。受控字符串和Visual C++ 7中的類(lèi)兩部分是Nish完成的。 為了更好的從這篇文章中受益,你必須要明白不同的字符類(lèi)型和編碼,這些內容我在第一部分中介紹過(guò)。 Rule #1 of string classes 使用cast來(lái)實(shí)現類(lèi)型轉換是不好的做法,除非有文檔明確指出這種轉換可以使用。 void SomeFunc ( LPCWSTR widestr );main(){ SomeFunc ( (LPCWSTR) "C:\\foo.txt" ); // WRONG!} 肯定會(huì )失敗。它可以被編譯,因為cast操作會(huì )撤消編譯器的類(lèi)型檢查。但是,編譯可以通過(guò)并不能說(shuō)明代碼是正確的。在下面的例子中,我將會(huì )指明cast在什么時(shí)候使用是合法的。 C-style strings and typedefs 正如我在第一部分中提到的,windows APIs 是用TCHARs來(lái)定義的,在編譯時(shí),它可以根據你是否定義_MBCS或者_UNICODE被編譯成MBCS或者Unicode字符。你可以參看第一部分中對TCHAR的完整描述,這里為了方便,我列出了字符的typedefs
一個(gè)增加的字符類(lèi)型是OLETYPE。它表示自動(dòng)化接口(如word提供的可以使你操作文檔的接口)中使用的字符類(lèi)型。這種類(lèi)型一般被定義成wchar_t,然而如果你定義了OLE2ANSI預處理標記,OLECHAR將會(huì )被定義成char類(lèi)型。我知道現在已經(jīng)沒(méi)有理由定義OLE2ANSI(從MFC3以后,微軟已經(jīng)不使用它了),所以從現在起我將把OLECHAR當作Unicode字符。
還有兩個(gè)用于包圍字符串和字符常量的宏定義,它們可以使同樣的代碼被用于MBCS和Unicode builds :
在文檔或例程中,你還會(huì )看到好多_T的變體。有四個(gè)等價(jià)的宏定義,它們是TEXT, _TEXT, __TEXT和__T,它們都起同樣的做用。 ![]() 很多自動(dòng)化和COM接口使用BSTR來(lái)定義字符串。BSTRs中有幾個(gè)"陷阱",所以這里我用單獨的部分來(lái)說(shuō)明它。 BSTR 是 Pascal-style 字符串(字符串長(cháng)度被明確指出)和C-style字符串(字符串的長(cháng)度要通過(guò)尋找結束符來(lái)計算)的混合產(chǎn)物。一個(gè)BSTR是一個(gè)Unicode字符串,它的長(cháng)度是預先考慮的,并且它還有一個(gè)0字符作為結束標記。下面是一個(gè)BSTR的示例:
注意字符串的長(cháng)度是如何被加到字符串數據中的。長(cháng)度是DWORD類(lèi)型的,保存了字符串中包含的字節數,但不包括結束標記。在這個(gè)例子中,"Bob"包含3個(gè)Unicode字符(不包括結束符),總共6個(gè)字節。字符串的長(cháng)度被預先存儲好,以便當一個(gè)BSTR在進(jìn)程或者計算機之間被傳遞時(shí),COM庫知道多少數據需要傳送。(另一方面,一個(gè)BSTR能夠存儲任意數據塊,而不僅僅是字符,它還可以包含嵌入在數據中的0字符。然而,由于這篇文章的目的,我將不考慮那些情況)。 BSTR bstr = NULL; bstr = SysAllocString ( L"Hi Bob!" ); if ( NULL == bstr ) // out of memory error // Use bstr here... SysFreeString ( bstr );自然的,各種各樣的BSTR封裝類(lèi)為你實(shí)現內存管理。 另外一個(gè)用在自動(dòng)化接口中的變量類(lèi)型是VARIANT。它被用來(lái)在無(wú)類(lèi)型(typeless)語(yǔ)言,如Jscript和VBScript,來(lái)傳遞數據。一個(gè)VARIANT可能含有很多不同類(lèi)型的數據,例如long和IDispatch*。當一個(gè)VARIANT包含一個(gè)字符串,字符串被存成一個(gè)BSTR。當我后面講到VARIANT封裝類(lèi)時(shí),我會(huì )對VARIANT多些介紹。 ![]() 到目前為止,我已經(jīng)介紹了各種各樣的字符串。下面,我將說(shuō)明封裝類(lèi)。對于每個(gè)封裝類(lèi),我將展示怎樣創(chuàng )建一個(gè)對象及怎樣把它轉換成一個(gè)C語(yǔ)言風(fēng)格的字符串指針。C語(yǔ)言風(fēng)格的字符串指針對于A(yíng)PI的調用,或者創(chuàng )建一個(gè)不同的字符串類(lèi)對象經(jīng)常是必需的。我不會(huì )介紹字符串類(lèi)提供的其他操作,比如排序和比較。 重復一遍,除非你確切的明白結果代碼將會(huì )做什么,否則不要盲目地使用cast來(lái)實(shí)現類(lèi)型轉換。 ![]() _bstr_t _bstr_t是一個(gè)對BSTR的完整封裝類(lèi),實(shí)際上它隱藏了底層的BSTR。它提供各種構造函數和操作符來(lái)訪(fǎng)問(wèn)底層的C語(yǔ)言風(fēng)格的字符串。然而,_bstr_t卻沒(méi)有訪(fǎng)問(wèn)BSTR本身的操作符,所以一個(gè)_bstr_t類(lèi)型的字符串不能被作為輸出參數傳給一個(gè)COM方法。如果你需要一個(gè)BSTR*參數,使用ATL類(lèi)CComBSTR是比較容易的方式。 一個(gè)_bstr_t字符串能夠傳給一個(gè)接收參數類(lèi)型為BSTR的函數,只是因為下列3個(gè)條件同時(shí)滿(mǎn)足。首先,_bstr_t有一個(gè)向wchar_t*轉換的轉換函數;其次,對編譯器而言,因為BSTR的定義,wchar_t*和BSTR有同樣的含義;第三,_bstr_t內部含有的wchar_t*指向一片按BSTR的形式存儲數據的內存。所以,即使沒(méi)有文檔說(shuō)明,_bstr_t可以轉換成BSTR,這種轉換仍然可以正常進(jìn)行。 // Constructing_bstr_t bs1 = "char string"; // construct from a LPCSTR_bstr_t bs2 = L"wide char string"; // construct from a LPCWSTR_bstr_t bs3 = bs1; // copy from another _bstr_t_variant_t v = "Bob";_bstr_t bs4 = v; // construct from a _variant_t that has a string // Extracting dataLPCSTR psz1 = bs1; // automatically converts to MBCS stringLPCSTR psz2 = (LPCSTR) bs1; // cast OK, same as previous lineLPCWSTR pwsz1 = bs1; // returns the internal Unicode stringLPCWSTR pwsz2 = (LPCWSTR) bs1; // cast OK, same as previous lineBSTR bstr = bs1.copy(); // copies bs1, returns it as a BSTR // ...SysFreeString ( bstr );注意_bstr_t也提供char*和wchar_t*之間的轉換操作符。這是一個(gè)值得懷疑的設計,因為即使它們是非常量字符串指針,你也一定不能使用這些指針去修改它們指向的緩沖區的內容,因為那將破壞內部的BSTR結構。 _variant_t _variant_t是一個(gè)對VARIANT的完整封裝,它提供很多構造函數和轉換函數來(lái)操作一個(gè)VARIANT可能包含的大量的數據類(lèi)型。這里,我將只介紹與字符串有關(guān)的操作。 // Constructing_variant_t v1 = "char string"; // construct from a LPCSTR_variant_t v2 = L"wide char string"; // construct from a LPCWSTR_bstr_t bs1 = "Bob";_variant_t v3 = bs1; // copy from a _bstr_t object // Extracting data_bstr_t bs2 = v1; // extract BSTR from the VARIANT_bstr_t bs3 = (_bstr_t) v1; // cast OK, same as previous line注意: 如果類(lèi)型轉換不能被執行,_variant_t方法能夠拋出異常,所以應該準備捕獲_com_error異常。 還需要注意的是: 沒(méi)有從一個(gè)_variant_t變量到一個(gè)MBCS字符串的直接轉換。你需要創(chuàng )建一個(gè)臨時(shí)的_bstr_t變量,使用提供Unicode到MBCS轉換的另一個(gè)字符串類(lèi)或者使用一個(gè)ATL轉換宏。 不像_bstr_t,一個(gè)_variant_t變量可以被直接作為參數傳遞給一個(gè)COM方法。_variant_t 繼承自VARIANT類(lèi)型,所以傳遞一個(gè)_variant_t來(lái)代替VARIANT變量是C++語(yǔ)言所允許的。 ![]() STL只有一個(gè)字符串類(lèi),basic_string。一個(gè)basic_string管理一個(gè)以0做結束符的字符串數組。字符的類(lèi)型是basic_string模般的參數??偟膩?lái)說(shuō),一個(gè)basic_string類(lèi)型的變量應該被當作不透明的對象。你可以得到一個(gè)指向內部緩沖區的只讀指針,但是任何寫(xiě)操作必須使用basic_string的操作符和方法。 basic_string有兩個(gè)預定義的類(lèi)型:包含char的string類(lèi)型和包含wchar_t的wstring類(lèi)型。這里沒(méi)有內置的包含TCHAR的類(lèi)型,但是你可以使用下面列出的代碼來(lái)實(shí)現。 // Specializationstypedef basic_string不像_bstr_t,一個(gè)basic_string變量不能在字符集之間直接轉換。然而,你可以傳遞由c_str()返回的指針給另外一個(gè)類(lèi)的構造函數(如果這個(gè)類(lèi)的構造函數接受這種字符類(lèi)型)。例如: // Example, construct _bstr_t from basic_string_bstr_t bs1 = str.c_str(); // construct a _bstr_t from a LPCSTR_bstr_t bs2 = wstr.c_str(); // construct a _bstr_t from a LPCWSTR ![]() CComBSTR CComBSTR 是 ATL 中的 BSTR 封裝類(lèi),它在某些情況下比_bstr_t有用的多。最引人注意的是CComBSTR允許訪(fǎng)問(wèn)底層的BSTR,這意味著(zhù)你可以傳遞一個(gè)CComBSTR對象給COM的方法。CComBSTR對象能夠替你自動(dòng)的管理BSTR的內存。例如,假設你想調用下面這個(gè)接口的方法: // Sample interface:struct IStuff : public IUnknown{ // Boilerplate COM stuff omitted... STDMETHOD(SetText)(BSTR bsText); STDMETHOD(GetText)(BSTR* pbsText);}; CComBSTR有一個(gè)操作符--BSTR方法,所以它能直接被傳給SetText()函數。還有另外一個(gè)操作--&,這個(gè)操作符返回一個(gè)BSTR*。所以,你可以對一個(gè)CComBSTR對象使用&操作符,然后把它傳給需要BSTR*參數的函數。 CComBSTR bs1;CComBSTR bs2 = "new text"; pStuff->GetText ( &bs1 ); // ok, takes address of internal BSTR pStuff->SetText ( bs2 ); // ok, calls BSTR converter pStuff->SetText ( (BSTR) bs2 ); // cast ok, same as previous lineCComBSTR有和_bstr_t相似的構造函數,然而卻沒(méi)有內置的向MBCS字符串轉換的函數。因此,你需要使用一個(gè)ATL轉換宏。 // ConstructingCComBSTR bs1 = "char string"; // construct from a LPCSTRCComBSTR bs2 = L"wide char string"; // construct from a LPCWSTRCComBSTR bs3 = bs1; // copy from another CComBSTRCComBSTR bs4; bs4.LoadString ( IDS_SOME_STR ); // load string from string table// Extracting dataBSTR bstr1 = bs1; // returns internal BSTR, but don‘‘t modify it!BSTR bstr2 = (BSTR) bs1; // cast ok, same as previous lineBSTR bstr3 = bs1.Copy(); // copies bs1, returns it as a BSTRBSTR bstr4; bstr4 = bs1.Detach(); // bs1 no longer manages its BSTR // ... SysFreeString ( bstr3 ); SysFreeString ( bstr4 );注意在上個(gè)例子中使用了Detach()方法。調用這個(gè)方法后,CComBSTR對象不再管理它的BSTR字符串或者說(shuō)它對應的內存。這就是bstr4需要調用SysFreeString()的原因。 做一個(gè)補充說(shuō)明:重載的&操作符意味著(zhù)在一些STL容器中你不能直接使用CComBSTR變量,比如list。容器要求&操作符返回一個(gè)指向容器包含的類(lèi)的指針,但是對CComBSTR變量使用&操作符返回的是BSTR*,而不是CComBSTR*。然而,有一個(gè)ATL類(lèi)可以解決這個(gè)問(wèn)題,這個(gè)類(lèi)是CAdapt。例如,你可以這樣聲明一個(gè)CComBSTR的list: std::list< CAdapt<CComBSTR> > bstr_list; CAdapt提供容器所需要的操作符,但這些操作符對你的代碼是透明的。你可以把一個(gè)bstr_list當作一個(gè)CComBSTR的list來(lái)使用。 // ConstructingCComVariant v1 = "char string"; // construct from a LPCSTRCComVariant v2 = L"wide char string"; // construct from a LPCWSTRCComBSTR bs1 = "BSTR bob";CComVariant v3 = (BSTR) bs1; // copy from a BSTR // Extracting dataCComBSTR bs2 = v1.bstrVal; // extract BSTR from the VARIANT不像_variant_t,這里沒(méi)有提供針對VARIANT包含的各種類(lèi)型的轉換操作符。正如上面介紹的,你必須直接訪(fǎng)問(wèn)VARIANT的成員并且確保這個(gè)VARIANT變量保存著(zhù)你期望的類(lèi)型。如果你需要把一個(gè)CComVariant類(lèi)型的數據轉換成一個(gè)BSTR類(lèi)型的數據,你可以調用ChangeType()方法。 CComVariant v4 = ... // Init v4 from somewhereCComBSTR bs3; if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) )) bs3 = v4.bstrVal;像_variant_t一樣,CComVariant也沒(méi)有提供向MBCS字符串轉換的轉換操作。你需要創(chuàng )建一個(gè)_bstr_t類(lèi)型的中間變量,使用提供從Unicode到MBCS轉換的另一個(gè)字符串類(lèi),或者使用一個(gè)ATL的轉換宏。 ![]() ATL:轉換宏是各種字符編碼之間進(jìn)行轉換的一種很方便的方式,在函數調用時(shí),它們顯得非常有用。ATL轉換宏的名稱(chēng)是根據下面的模式來(lái)命名的[源類(lèi)型]2[新類(lèi)型]或者[源類(lèi)型]2C[新類(lèi)型]。據有第二種形式的名字的宏的轉換結果是常量指針(對應名字中的"C")。各種類(lèi)型的簡(jiǎn)稱(chēng)如下: A: MBCS string, char* (A for ANSI)W: Unicode string, wchar_t* (W for wide)T: TCHAR string, TCHAR*OLE: OLECHAR string, OLECHAR* (in practice, equivalent to W)BSTR: BSTR (used as the destination type only) 所以,W2A()宏把一個(gè)Unicode字符串轉換成一個(gè)MBCS字符串。T2CW()宏把一個(gè)TCHAR字符串轉轉成一個(gè)Unicode字符串常量。 // Functions taking various strings:void Foo ( LPCWSTR wstr );void Bar ( BSTR bstr );// Functions returning strings:void Baz ( BSTR* pbstr );#include <atlconv.h>main(){using std::string;USES_CONVERSION; // declare locals used by the ATL macros// Example 1: Send an MBCS string to Foo()LPCSTR psz1 = "Bob";string str1 = "Bob"; Foo ( A2CW(psz1) ); Foo ( A2CW(str1.c_str()) ); // Example 2: Send a MBCS and Unicode string to Bar()LPCSTR psz2 = "Bob";LPCWSTR wsz = L"Bob";BSTR bs1;CComBSTR bs2; bs1 = A2BSTR(psz2); // create a BSTR bs2.Attach ( W2BSTR(wsz) ); // ditto, assign to a CComBSTR Bar ( bs1 ); Bar ( bs2 ); SysFreeString ( bs1 ); // free bs1 memory // No need to free bs2 since CComBSTR will do it for us. // Example 3: Convert the BSTR returned by Baz()BSTR bs3 = NULL;string str2; Baz ( &bs3 ); // Baz() fills in bs3 str2 = W2CA(bs3); // convert to an MBCS string SysFreeString ( bs3 ); // free bs3 memory} 正如你所看見(jiàn)的,當你有一個(gè)和函數所需的參數類(lèi)型不同的字符串時(shí),使用這些轉換宏是非常方便的。 ![]() CString 因為一個(gè)MFC CString類(lèi)的對象包含TCHAR類(lèi)型的字符,所以確切的字符類(lèi)型取決于你所定義的預處理符號。大體來(lái)說(shuō),CString 很像STL string,這意味著(zhù)你必須把它當成不透明的對象,只能使用CString提供的方法來(lái)修改CString對象。CString有一個(gè)string所不具備的優(yōu)點(diǎn):CString具有接收MBCS和Unicode兩種字符串的構造函數,它還有一個(gè)LPCTSTR轉換符,所以你可以把CString對象直接傳給一個(gè)接收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 );你可以從你的字符串表中裝載一個(gè)字符串,CString的一個(gè)構造函數和LoadString()函數可以完成它。Format()方法能夠從字符串表中隨意的讀取一個(gè)具有一定格式的字符串?! ? // 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, ... );第一個(gè)構造函數看起來(lái)有點(diǎn)奇怪,但是這實(shí)際上是文檔說(shuō)明的裝入一個(gè)字符串的方法。 注意,對一個(gè)CString變量,你可以使用的唯一合法轉換符是LPCTSTR。轉換成LPTSTR(非常量指針)是錯誤的。養成把一個(gè)CString變量轉換成LPTSTR的習慣將會(huì )給你帶來(lái)傷害,因為當你的程序后來(lái)崩潰時(shí),你可能不知道為什么,因為你到處都使用同樣的代碼而那時(shí)它們都恰巧正常工作。正確的得到一個(gè)指向緩沖區的非常量指針的方法是調用GetBuffer()方法。下面是正確的用法的一個(gè)例子,這段代碼是給一個(gè)列表控件中的項設定文字: 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成員是一個(gè)LPTSTR變量,一個(gè)非常量指針,因此你需要對str調用GetBuffer()。GetBuffer()的參數是你需要CString為緩沖區分配的最小長(cháng)度。如果因為某些原因,你需要一個(gè)可修改的緩沖區來(lái)存放1K TCHARs,你需要調用GetBuffer(1024)。把0作為參數時(shí),GetBuffer()返回的是指向字符串當前內容的指針。上面劃線(xiàn)的語(yǔ)句可以被編譯,在這種情況下,甚至可以正常起作用。但這并不意味著(zhù)這行代碼是正確的。通過(guò)使用非常量轉換,你已經(jīng)破壞了面向對象的封裝,并對CString的內部實(shí)現作了某些假定。如果你有這樣的轉換習慣,你終將會(huì )陷入代碼崩潰的境地。你會(huì )想代碼為什么不能正常工作了,因為你到處都使用同樣的代碼而那些代碼看起來(lái)是正確的。 你知道人們總是抱怨現在的軟件的bug是多么的多嗎?軟件中的bug是因為程序員寫(xiě)了不正確的代碼。難道你真的想寫(xiě)一些你知道是錯誤的代碼來(lái)為所有的軟件都滿(mǎn)是bug這種認識做貢獻嗎?花些時(shí)間來(lái)學(xué)習使用CString的正確方法讓你的代碼在任何時(shí)間都正常工作把。 CString 有兩個(gè)函數來(lái)從一個(gè) CString 創(chuàng )建一個(gè) 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只有一個(gè)LPCTSTR構造函數。沒(méi)有對LPCSTR 和LPCWSTR的構造函數。在大多數情況下這不是一個(gè)問(wèn)題,因為不管怎樣你的字符串很可能是LPCTSTRs,但這是一個(gè)需要意識到的問(wèn)題。COleVariant還有一個(gè)接收CString參數的構造函數。 // ConstructingCString s1 = _T("tchar string");COleVariant v1 = _T("Bob"); // construct from an LPCTSTRCOleVariant v2 = s1; // copy from a CString 像CComVariant一樣,你必須直接訪(fǎng)問(wèn)VARIANT的成員。如果需要把VARIANT轉換成一個(gè)字符串,你應該使用ChangeType()方法。然而,COleVariant::ChangeType()如果失敗會(huì )拋出異常,而不是返回一個(gè)表示失敗的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 ); ![]() CString WTL的CString的行為和MFC的 CString完全一樣,所以你可以參考上面關(guān)于MFC的 CString的介紹。 ![]() System::String是用來(lái)處理字符串的.NET類(lèi)。在內部,一個(gè)String對象包含一個(gè)不可改變的字符串序列。任何對String對象的操作實(shí)際上都是返回了一個(gè)新的String對象,因為原始的對象是不可改變的。String的一個(gè)特性是如果你有不止一個(gè)String對象包含相同的字符序列,它們實(shí)際上是指向相同的對象的。相對于C++的使用擴展是增加了一個(gè)新的字符串常量前綴S,S用來(lái)代表一個(gè)受控的字符串常量(a managed string literal)。 // ConstructingString* ms = S"This is a nice managed string";你可以傳遞一個(gè)非受控的字符串來(lái)創(chuàng )建一個(gè)String對象,但是樣會(huì )比使用受控字符串來(lái)創(chuàng )建String對象造成效率的微小損失。這是因為所有以S作為前綴的相同的字符串實(shí)例都代表同樣的對象,但這對非受控對象是不適用的。下面的代碼清楚地闡明了這一點(diǎn): 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正確的比較可能沒(méi)有使用S前綴的字符串的方法是使用String::CompareTo() Console::WriteLine ( ms1->CompareTo(ms2) ); Console::WriteLine ( ms1->CompareTo(ms3) );上面的兩行代碼都會(huì )打印0,0表示兩個(gè)字符串相等。 String和MFC 7 CString之間的轉換是很容易的。CString有一個(gè)向LPCTSTR的轉換操作,而String有兩個(gè)接收char* 和 wchar_t*的構造函數,因此你可以把一個(gè)CString變量直接傳給一個(gè)String的構造函數。 CString s1 ( "hello world" );String* s2 ( s1 ); // copy from a CString反方向的轉換也很類(lèi)似 String* s1 = S"Three cats";CString s2 ( s1 );這也許會(huì )使你感到一點(diǎn)迷惑,但是它確實(shí)是起作用的。因為從VS.NET 開(kāi)始,CString 有了一個(gè)接收String 對象的構造函數。 CStringT ( System::String* pString );對于一些快速操作,你可能想訪(fǎng)問(wèn)底層的字符串: 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()返回一個(gè)指向底層字符串的const __wchar_t* ,我們需要固定它,否則垃圾收集器或許會(huì )在我們正在管理它的內容的時(shí)候移動(dòng)了它。 ![]() 當你在printf()或者類(lèi)似的函數中使用字符串封裝類(lèi)時(shí)你必須十分小心。這些函數包括sprintf()和它的變體,還有TRACE和ATLTRACE宏。因為這些函數沒(méi)有對添加的參數的類(lèi)型檢查,你必須小心,只能傳給它們C語(yǔ)言風(fēng)格的字符串指針,而不是一個(gè)完整的字符串類(lèi)。 例如,要把一個(gè)_bstr_t 字符串傳給ATLTRACE(),你必須使用顯式轉換(LPCSTR) 或者(LPCWSTR): _bstr_t bs = L"Bob!";ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine); 如果你忘了使用轉換符而把整個(gè)_bstr_t對象傳給了函數,將會(huì )顯示一些毫無(wú)意義的輸出,因為_(kāi)bstr_t保存的內部數據會(huì )全部被輸出。 ![]() 兩個(gè)字符串類(lèi)之間進(jìn)行轉換的常用方式是:先把源字符串轉換成一個(gè)C語(yǔ)言風(fēng)格的字符串指針,然后把這個(gè)指針傳遞給目的類(lèi)型的構造函數。下面這張表顯示了怎樣把一個(gè)字符串轉換成一個(gè)C語(yǔ)言風(fēng)格的字符串指針以及哪些類(lèi)具有接收C語(yǔ)言風(fēng)格的字符串指針的構造函數。
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() Michael Dunn: Michael Dunn居住在陽(yáng)光城市洛杉磯。他是如此的喜歡這里的天氣以致于想一生都住在這里。他在4年級時(shí)開(kāi)始編程,那時(shí)用的電腦是Apple //e。1995年,在UCLA獲得數學(xué)學(xué)士學(xué)位,隨后在Symantec公司做QA工程師,在 Norton AntiVirus 組工作。他自學(xué)了 Windows 和 MFC 編程。1999-2000年,他設計并實(shí)現了 Norton AntiVirus的新界面。 Michael 現在在 Napster(一個(gè)提供在線(xiàn)訂閱音樂(lè )服務(wù)的公司)做開(kāi)發(fā)工作,他還開(kāi)發(fā)了UltraBar,一個(gè)IE工具欄插件,它可以使網(wǎng)絡(luò )搜索更加容易,給了 googlebar 以沉重打擊;他還開(kāi)發(fā)了 CodeProject SearchBar;與人共同創(chuàng )建了 Zabersoft 公司,該公司在洛杉磯和丹麥的 Odense 都設有辦事處。 他喜歡玩游戲。愛(ài)玩的游戲有 pinball, bike riding,偶爾還玩 PS, Dreamcasth 和 MAME 游戲。他因忘了自己曾經(jīng)學(xué)過(guò)的語(yǔ)言:法語(yǔ)、漢語(yǔ)、日語(yǔ)而感到悲哀。 Nishant S(Nish): Nish是來(lái)自印度 Trivandrum,的 Microsoft Visual C++ MVP。他從1990年開(kāi)始編碼?,F在,Nish為作為合同雇員在家里為 CodeProject 工作?! ?br> 他還寫(xiě)了一部浪漫戲劇《 Nish 還計劃好 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
聯(lián)系客服