C++字符串完全指引之二 —— 字符串封裝類
原著:Michael Dunn
作者:Chengjie Sun
原文出處:CodeProject:The Complete Guide to C++ Strings, Part II
引言
因?yàn)镃語言風(fēng)格的字符串容易出錯(cuò)且不易管理,黑客們甚至利用可能存在的緩沖區(qū)溢出bug把C語言風(fēng)格的字符串作為攻擊目標(biāo),所以出現(xiàn)了很多字符串封裝類。不幸的是,在某些場合下我們不知道該使用哪個(gè)字符串類,也不知道怎樣把一個(gè)C風(fēng)格的字符串轉(zhuǎn)換成一個(gè)字符串封裝類。 這篇文章將介紹所有在Win32 API, MFC, STL, WTL 和 Visual C++ 運(yùn)行庫中出現(xiàn)的字符串類型。我將描述每一個(gè)類的用法,告訴大家怎樣創(chuàng)建每一個(gè)類的對(duì)象以及怎樣把一個(gè)類轉(zhuǎn)換成其他類。受控字符串和Visual C++ 7中的類兩部分是Nish完成的。 為了更好的從這篇文章中受益,你必須要明白不同的字符類型和編碼,這些內(nèi)容我在第一部分中介紹過。
Rule #1 of string classes
使用cast來實(shí)現(xiàn)類型轉(zhuǎn)換是不好的做法,除非有文檔明確指出這種轉(zhuǎn)換可以使用。 促使我寫這兩篇文章的原因是字符串類型轉(zhuǎn)換中經(jīng)常遇到的一些問題。當(dāng)我們使用cast把字符串從類型X轉(zhuǎn)換到類型Z的時(shí)候,我們不知道為什么代碼不能正常工作。各種各樣的字符串類型,尤其是BSTR,幾乎沒有在任何一個(gè)地方的文檔中被明確的指出可以用cast來實(shí)現(xiàn)類型轉(zhuǎn)換。所以我想一些人可能會(huì)使用cast來實(shí)現(xiàn)類型轉(zhuǎn)換并希望這種轉(zhuǎn)換能夠正常工作。 除非源字符串是一個(gè)被明確指明支持轉(zhuǎn)換操作符的字符串包裝類,否則cast不對(duì)字符串做任何轉(zhuǎn)換。對(duì)常量字符串使用cast不會(huì)起到任何作用,所以下面的代碼:
void SomeFunc ( LPCWSTR widestr );
main()
{
SomeFunc ( (LPCWSTR) "C:\\foo.txt" ); // WRONG!
}
肯定會(huì)失敗。它可以被編譯,因?yàn)閏ast操作會(huì)撤消編譯器的類型檢查。但是,編譯可以通過并不能說明代碼是正確的。 在下面的例子中,我將會(huì)指明cast在什么時(shí)候使用是合法的。
C-style strings and typedefs
正如我在第一部分中提到的,windows APIs 是用TCHARs來定義的,在編譯時(shí),它可以根據(jù)你是否定義_MBCS或者_(dá)UNICODE被編譯成MBCS或者Unicode字符。你可以參看第一部分中對(duì)TCHAR的完整描述,這里為了方便,我列出了字符的typedefs
Type |
Meaning |
WCHAR |
Unicode character (wchar_t) |
TCHAR |
MBCS or Unicode character, depending on preprocessor settings |
LPSTR |
string of char (char*) |
LPCSTR |
constant string of char (const char*) |
LPWSTR |
string of WCHAR (WCHAR*) |
LPCWSTR |
constant string of WCHAR (const WCHAR*) |
LPTSTR |
string of TCHAR (TCHAR*) |
LPCTSTR |
constant string of TCHAR (const TCHAR*) |
一個(gè)增加的字符類型是OLETYPE。它表示自動(dòng)化接口(如word提供的可以使你操作文檔的接口)中使用的字符類型。這種類型一般被定義成wchar_t,然而如果你定義了OLE2ANSI預(yù)處理標(biāo)記,OLECHAR將會(huì)被定義成char類型。我知道現(xiàn)在已經(jīng)沒有理由定義OLE2ANSI(從MFC3以后,微軟已經(jīng)不使用它了),所以從現(xiàn)在起我將把OLECHAR當(dāng)作Unicode字符。 這里給出你將會(huì)看到的一些OLECHAR相關(guān)的typedefs:
Type |
Meaning |
OLECHAR |
Unicode character (wchar_t) |
LPOLESTR |
string of OLECHAR (OLECHAR*) |
LPCOLESTR |
constant string of OLECHAR (const OLECHAR*) |
還有兩個(gè)用于包圍字符串和字符常量的宏定義,它們可以使同樣的代碼被用于MBCS和Unicode builds :
Type |
Meaning |
_T(x) |
Prepends L to the literal in Unicode builds. |
OLESTR(x) |
Prepends L to the literal to make it an LPCOLESTR. |
在文檔或例程中,你還會(huì)看到好多_T的變體。有四個(gè)等價(jià)的宏定義,它們是TEXT, _TEXT, __TEXT和__T,它們都起同樣的做用。
COM 中的字符串 —— BSTR 和 VARIANT
很多自動(dòng)化和COM接口使用BSTR來定義字符串。BSTRs中有幾個(gè)"陷阱",所以這里我用單獨(dú)的部分來說明它。 BSTR 是 Pascal-style 字符串(字符串長度被明確指出)和C-style字符串(字符串的長度要通過尋找結(jié)束符來計(jì)算)的混合產(chǎn)物。一個(gè)BSTR是一個(gè)Unicode字符串,它的長度是預(yù)先考慮的,并且它還有一個(gè)0字符作為結(jié)束標(biāo)記。下面是一個(gè)BSTR的示例:
06 00 00 00 |
42 00 |
6F 00 |
62 00 |
00 00 |
--length-- |
B |
o |
b |
EOS |
注意字符串的長度是如何被加到字符串?dāng)?shù)據(jù)中的。長度是DWORD類型的,保存了字符串中包含的字節(jié)數(shù),但不包括結(jié)束標(biāo)記。在這個(gè)例子中,"Bob"包含3個(gè)Unicode字符(不包括結(jié)束符),總共6個(gè)字節(jié)。字符串的長度被預(yù)先存儲(chǔ)好,以便當(dāng)一個(gè)BSTR在進(jìn)程或者計(jì)算機(jī)之間被傳遞時(shí),COM庫知道多少數(shù)據(jù)需要傳送。(另一方面,一個(gè)BSTR能夠存儲(chǔ)任意數(shù)據(jù)塊,而不僅僅是字符,它還可以包含嵌入在數(shù)據(jù)中的0字符。然而,由于這篇文章的目的,我將不考慮那些情況)。 在 C++ 中,一個(gè) BSTR 實(shí)際上就是一個(gè)指向字符串中第一個(gè)字符的指針。它的定義如下:
BSTR bstr = NULL;
bstr = SysAllocString ( L"Hi Bob!" );
if ( NULL == bstr )
// out of memory error
// Use bstr here...
SysFreeString ( bstr );
自然的,各種各樣的BSTR封裝類為你實(shí)現(xiàn)內(nèi)存管理。 另外一個(gè)用在自動(dòng)化接口中的變量類型是VARIANT。它被用來在無類型(typeless)語言,如Jscript和VBScript,來傳遞數(shù)據(jù)。一個(gè)VARIANT可能含有很多不同類型的數(shù)據(jù),例如long和IDispatch*。當(dāng)一個(gè)VARIANT包含一個(gè)字符串,字符串被存成一個(gè)BSTR。當(dāng)我后面講到VARIANT封裝類時(shí),我會(huì)對(duì)VARIANT多些介紹。
字符串封裝類
到目前為止,我已經(jīng)介紹了各種各樣的字符串。下面,我將說明封裝類。對(duì)于每個(gè)封裝類,我將展示怎樣創(chuàng)建一個(gè)對(duì)象及怎樣把它轉(zhuǎn)換成一個(gè)C語言風(fēng)格的字符串指針。C語言風(fēng)格的字符串指針對(duì)于API的調(diào)用,或者創(chuàng)建一個(gè)不同的字符串類對(duì)象經(jīng)常是必需的。我不會(huì)介紹字符串類提供的其他操作,比如排序和比較。 重復(fù)一遍,除非你確切的明白結(jié)果代碼將會(huì)做什么,否則不要盲目地使用cast來實(shí)現(xiàn)類型轉(zhuǎn)換。
CRT提供的類
_bstr_t _bstr_t是一個(gè)對(duì)BSTR的完整封裝類,實(shí)際上它隱藏了底層的BSTR。它提供各種構(gòu)造函數(shù)和操作符來訪問底層的C語言風(fēng)格的字符串。然而,_bstr_t卻沒有訪問BSTR本身的操作符,所以一個(gè)_bstr_t類型的字符串不能被作為輸出參數(shù)傳給一個(gè)COM方法。如果你需要一個(gè)BSTR*參數(shù),使用ATL類CComBSTR是比較容易的方式。 一個(gè)_bstr_t字符串能夠傳給一個(gè)接收參數(shù)類型為BSTR的函數(shù),只是因?yàn)橄铝?個(gè)條件同時(shí)滿足。首先,_bstr_t有一個(gè)向wchar_t*轉(zhuǎn)換的轉(zhuǎn)換函數(shù);其次,對(duì)編譯器而言,因?yàn)锽STR的定義,wchar_t*和BSTR有同樣的含義;第三,_bstr_t內(nèi)部含有的wchar_t*指向一片按BSTR的形式存儲(chǔ)數(shù)據(jù)的內(nèi)存。所以,即使沒有文檔說明,_bstr_t可以轉(zhuǎn)換成BSTR,這種轉(zhuǎn)換仍然可以正常進(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 data
LPCSTR psz1 = bs1; // automatically converts to MBCS string
LPCSTR psz2 = (LPCSTR) bs1; // cast OK, same as previous line
LPCWSTR pwsz1 = bs1; // returns the internal Unicode string
LPCWSTR pwsz2 = (LPCWSTR) bs1; // cast OK, same as previous line
BSTR bstr = bs1.copy(); // copies bs1, returns it as a BSTR
// ...
SysFreeString ( bstr );
注意_bstr_t也提供char*和wchar_t*之間的轉(zhuǎn)換操作符。這是一個(gè)值得懷疑的設(shè)計(jì),因?yàn)榧词顾鼈兪欠浅A孔址羔槪阋惨欢ú荒苁褂眠@些指針去修改它們指向的緩沖區(qū)的內(nèi)容,因?yàn)槟菍⑵茐膬?nèi)部的BSTR結(jié)構(gòu)。
_variant_t _variant_t是一個(gè)對(duì)VARIANT的完整封裝,它提供很多構(gòu)造函數(shù)和轉(zhuǎn)換函數(shù)來操作一個(gè)VARIANT可能包含的大量的數(shù)據(jù)類型。這里,我將只介紹與字符串有關(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
注意: 如果類型轉(zhuǎn)換不能被執(zhí)行,_variant_t方法能夠拋出異常,所以應(yīng)該準(zhǔn)備捕獲_com_error異常。
還需要注意的是: 沒有從一個(gè)_variant_t變量到一個(gè)MBCS字符串的直接轉(zhuǎn)換。你需要?jiǎng)?chuàng)建一個(gè)臨時(shí)的_bstr_t變量,使用提供Unicode到MBCS轉(zhuǎn)換的另一個(gè)字符串類或者使用一個(gè)ATL轉(zhuǎn)換宏。 不像_bstr_t,一個(gè)_variant_t變量可以被直接作為參數(shù)傳遞給一個(gè)COM方法。_variant_t 繼承自VARIANT類型,所以傳遞一個(gè)_variant_t來代替VARIANT變量是C++語言所允許的。
STL 類 STL只有一個(gè)字符串類,basic_string。一個(gè)basic_string管理一個(gè)以0做結(jié)束符的字符串?dāng)?shù)組。字符的類型是basic_string模般的參數(shù)。總的來說,一個(gè)basic_string類型的變量應(yīng)該被當(dāng)作不透明的對(duì)象。你可以得到一個(gè)指向內(nèi)部緩沖區(qū)的只讀指針,但是任何寫操作必須使用basic_string的操作符和方法。 basic_string有兩個(gè)預(yù)定義的類型:包含char的string類型和包含wchar_t的wstring類型。這里沒有內(nèi)置的包含TCHAR的類型,但是你可以使用下面列出的代碼來實(shí)現(xiàn)。
// Specializations
typedef basic_string tstring; // string of TCHARs
// Constructing
string str = "char string"; // construct from a LPCSTR
wstring wstr = L"wide char string"; // construct from a LPCWSTR
tstring tstr = _T("TCHAR string"); // construct from a LPCTSTR
// Extracting data
LPCSTR psz = str.c_str(); // read-only pointer to str''s buffer
LPCWSTR pwsz = wstr.c_str(); // read-only pointer to wstr''s buffer
LPCTSTR ptsz = tstr.c_str(); // read-only pointer to tstr''s buffer
不像_bstr_t,一個(gè)basic_string變量不能在字符集之間直接轉(zhuǎn)換。然而,你可以傳遞由c_str()返回的指針給另外一個(gè)類的構(gòu)造函數(shù)(如果這個(gè)類的構(gòu)造函數(shù)接受這種字符類型)。例如:
// 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
ATL 類
CComBSTR CComBSTR 是 ATL 中的 BSTR 封裝類,它在某些情況下比_bstr_t有用的多。最引人注意的是CComBSTR允許訪問底層的BSTR,這意味著你可以傳遞一個(gè)CComBSTR對(duì)象給COM的方法。CComBSTR對(duì)象能夠替你自動(dòng)的管理BSTR的內(nèi)存。例如,假設(shè)你想調(diào)用下面這個(gè)接口的方法:
// Sample interface:
struct IStuff : public IUnknown
{
// Boilerplate COM stuff omitted...
STDMETHOD(SetText)(BSTR bsText);
STDMETHOD(GetText)(BSTR* pbsText);
};
CComBSTR有一個(gè)操作符--BSTR方法,所以它能直接被傳給SetText()函數(shù)。還有另外一個(gè)操作--&,這個(gè)操作符返回一個(gè)BSTR*。所以,你可以對(duì)一個(gè)CComBSTR對(duì)象使用&操作符,然后把它傳給需要BSTR*參數(shù)的函數(shù)。
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 line
CComBSTR有和_bstr_t相似的構(gòu)造函數(shù),然而卻沒有內(nèi)置的向MBCS字符串轉(zhuǎn)換的函數(shù)。因此,你需要使用一個(gè)ATL轉(zhuǎn)換宏。
// Constructing
CComBSTR bs1 = "char string"; // construct from a LPCSTR
CComBSTR bs2 = L"wide char string"; // construct from a LPCWSTR
CComBSTR bs3 = bs1; // copy from another CComBSTR
CComBSTR bs4;
bs4.LoadString ( IDS_SOME_STR ); // load string from string table
// Extracting data
BSTR bstr1 = bs1; // returns internal BSTR, but don''t modify it!
BSTR bstr2 = (BSTR) bs1; // cast ok, same as previous line
BSTR bstr3 = bs1.Copy(); // copies bs1, returns it as a BSTR
BSTR bstr4;
bstr4 = bs1.Detach(); // bs1 no longer manages its BSTR
// ...
SysFreeString ( bstr3 );
SysFreeString ( bstr4 );
注意在上個(gè)例子中使用了Detach()方法。調(diào)用這個(gè)方法后,CComBSTR對(duì)象不再管理它的BSTR字符串或者說它對(duì)應(yīng)的內(nèi)存。這就是bstr4需要調(diào)用SysFreeString()的原因。 做一個(gè)補(bǔ)充說明:重載的&操作符意味著在一些STL容器中你不能直接使用CComBSTR變量,比如list。容器要求&操作符返回一個(gè)指向容器包含的類的指針,但是對(duì)CComBSTR變量使用&操作符返回的是BSTR*,而不是CComBSTR*。然而,有一個(gè)ATL類可以解決這個(gè)問題,這個(gè)類是CAdapt。例如,你可以這樣聲明一個(gè)CComBSTR的list:
std::list< CAdapt > bstr_list;
CAdapt提供容器所需要的操作符,但這些操作符對(duì)你的代碼是透明的。你可以把一個(gè)bstr_list當(dāng)作一個(gè)CComBSTR的list來使用。
CComVariant CComVariant是VARIANT的封裝類。然而,不像_variant_t,在CComVariant中VARIANT沒有被隱藏。事實(shí)上你需要直接訪問VARIANT的成員。CComVariant提供了很多構(gòu)造函數(shù)來對(duì)VARIANT能夠包含的多種類型進(jìn)行處理。這里,我將只介紹和字符串相關(guān)的操作。
// Constructing
CComVariant v1 = "char string"; // construct from a LPCSTR
CComVariant v2 = L"wide char string"; // construct from a LPCWSTR
CComBSTR bs1 = "BSTR bob";
CComVariant v3 = (BSTR) bs1; // copy from a BSTR
// Extracting data
CComBSTR bs2 = v1.bstrVal; // extract BSTR from the VARIANT
不像_variant_t,這里沒有提供針對(duì)VARIANT包含的各種類型的轉(zhuǎn)換操作符。正如上面介紹的,你必須直接訪問VARIANT的成員并且確保這個(gè)VARIANT變量保存著你期望的類型。如果你需要把一個(gè)CComVariant類型的數(shù)據(jù)轉(zhuǎn)換成一個(gè)BSTR類型的數(shù)據(jù),你可以調(diào)用ChangeType()方法。
CComVariant v4 = ... // Init v4 from somewhere
CComBSTR bs3;
if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) ))
bs3 = v4.bstrVal;
像_variant_t一樣,CComVariant也沒有提供向MBCS字符串轉(zhuǎn)換的轉(zhuǎn)換操作。你需要?jiǎng)?chuàng)建一個(gè)_bstr_t類型的中間變量,使用提供從Unicode到MBCS轉(zhuǎn)換的另一個(gè)字符串類,或者使用一個(gè)ATL的轉(zhuǎn)換宏。
ATL轉(zhuǎn)換宏
ATL:轉(zhuǎn)換宏是各種字符編碼之間進(jìn)行轉(zhuǎn)換的一種很方便的方式,在函數(shù)調(diào)用時(shí),它們顯得非常有用。ATL轉(zhuǎn)換宏的名稱是根據(jù)下面的模式來命名的[源類型]2[新類型]或者[源類型]2C[新類型]。據(jù)有第二種形式的名字的宏的轉(zhuǎn)換結(jié)果是常量指針(對(duì)應(yīng)名字中的"C")。各種類型的簡稱如下:
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字符串轉(zhuǎn)換成一個(gè)MBCS字符串。T2CW()宏把一個(gè)TCHAR字符串轉(zhuǎn)轉(zhuǎn)成一個(gè)Unicode字符串常量。 為了使用這些宏,需要先包含atlconv.h頭文件。你甚至可以在非ATL工程中包含這個(gè)頭文件來使用其中定義的宏,因?yàn)檫@個(gè)頭文件獨(dú)立于ATL中的其他部分,不需要一個(gè)_Module全局變量。當(dāng)你在一個(gè)函數(shù)中使用轉(zhuǎn)換宏時(shí),需要把USES_CONVERSION宏放在函數(shù)的開頭。它定義了轉(zhuǎn)換宏所需的一些局部變量。 當(dāng)轉(zhuǎn)換的目的類型是除了BSTR以外的其他類型時(shí),被轉(zhuǎn)換的字符串是存在棧中的。所以,如果你想讓字符串的生命周期比當(dāng)前的函數(shù)長,你需要把這個(gè)字符串拷貝到其他的字符串類中。當(dāng)目的類型是BSTR時(shí),內(nèi)存不會(huì)自動(dòng)被釋放,你必須把返回值賦給一個(gè)BSTR變量或者一個(gè)BSTR封裝類以避免內(nèi)存泄漏。 下面是一些各種轉(zhuǎn)換宏的使用例子:
// Functions taking various strings:
void Foo ( LPCWSTR wstr );
void Bar ( BSTR bstr );
// Functions returning strings:
void Baz ( BSTR* pbstr );
#include
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
}
正如你所看見的,當(dāng)你有一個(gè)和函數(shù)所需的參數(shù)類型不同的字符串時(shí),使用這些轉(zhuǎn)換宏是非常方便的。
MFC類
CString 因?yàn)橐粋€(gè)MFC CString類的對(duì)象包含TCHAR類型的字符,所以確切的字符類型取決于你所定義的預(yù)處理符號(hào)。大體來說,CString 很像STL string,這意味著你必須把它當(dāng)成不透明的對(duì)象,只能使用CString提供的方法來修改CString對(duì)象。CString有一個(gè)string所不具備的優(yōu)點(diǎn):CString具有接收MBCS和Unicode兩種字符串的構(gòu)造函數(shù),它還有一個(gè)LPCTSTR轉(zhuǎn)換符,所以你可以把CString對(duì)象直接傳給一個(gè)接收LPCTSTR的函數(shù)而不需要調(diào)用c_str()函數(shù)。
// Constructing
CString s1 = "char string"; // construct from a LPCSTR
CString s2 = L"wide char string"; // construct from a LPCWSTR
CString s3 ( '' '', 100 ); // pre-allocate a 100-byte buffer, fill with spaces
CString 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è)構(gòu)造函數(shù)和LoadString()函數(shù)可以完成它。Format()方法能夠從字符串表中隨意的讀取一個(gè)具有一定格式的字符串。
// Constructing/loading from string table
CString s5 ( (LPCTSTR) IDS_SOME_STR ); // load from string table
CString 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è)構(gòu)造函數(shù)看起來有點(diǎn)奇怪,但是這實(shí)際上是文檔說明的裝入一個(gè)字符串的方法。 注意,對(duì)一個(gè)CString變量,你可以使用的唯一合法轉(zhuǎn)換符是LPCTSTR。轉(zhuǎn)換成LPTSTR(非常量指針)是錯(cuò)誤的。養(yǎng)成把一個(gè)CString變量轉(zhuǎn)換成LPTSTR的習(xí)慣將會(huì)給你帶來傷害,因?yàn)楫?dāng)你的程序后來崩潰時(shí),你可能不知道為什么,因?yàn)槟愕教幎际褂猛瑯拥拇a而那時(shí)它們都恰巧正常工作。正確的得到一個(gè)指向緩沖區(qū)的非常量指針的方法是調(diào)用GetBuffer()方法。下面是正確的用法的一個(gè)例子,這段代碼是給一個(gè)列表控件中的項(xiàng)設(shè)定文字:
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è)非常量指針,因此你需要對(duì)str調(diào)用GetBuffer()。GetBuffer()的參數(shù)是你需要CString為緩沖區(qū)分配的最小長度。如果因?yàn)槟承┰颍阈枰粋€(gè)可修改的緩沖區(qū)來存放1K TCHARs,你需要調(diào)用GetBuffer(1024)。把0作為參數(shù)時(shí),GetBuffer()返回的是指向字符串當(dāng)前內(nèi)容的指針。 上面劃線的語句可以被編譯,在這種情況下,甚至可以正常起作用。但這并不意味著這行代碼是正確的。通過使用非常量轉(zhuǎn)換,你已經(jīng)破壞了面向?qū)ο蟮姆庋b,并對(duì)CString的內(nèi)部實(shí)現(xiàn)作了某些假定。如果你有這樣的轉(zhuǎn)換習(xí)慣,你終將會(huì)陷入代碼崩潰的境地。你會(huì)想代碼為什么不能正常工作了,因?yàn)槟愕教幎际褂猛瑯拥拇a而那些代碼看起來是正確的。 你知道人們總是抱怨現(xiàn)在的軟件的bug是多么的多嗎?軟件中的bug是因?yàn)槌绦騿T寫了不正確的代碼。難道你真的想寫一些你知道是錯(cuò)誤的代碼來為所有的軟件都滿是bug這種認(rèn)識(shí)做貢獻(xiàn)嗎?花些時(shí)間來學(xué)習(xí)使用CString的正確方法讓你的代碼在任何時(shí)間都正常工作把。 CString 有兩個(gè)函數(shù)來從一個(gè) CString 創(chuàng)建一個(gè) BSTR。它們是 AllocSysString() 和SetSysString()。
// Converting to BSTR
CString s5 = "Bob!";
BSTR bs1 = NULL, bs2 = NULL;
bs1 = s5.AllocSysString();
s5.SetSysString ( &bs2 );
SysFreeString ( bs1 );
SysFreeString ( bs2 );
COleVariant COleVariant和CComVariant.很相似。COleVariant繼承自VARIANT,所以它可以傳給接收VARIANT的函數(shù)。然而,不像CComVariant,COleVariant只有一個(gè)LPCTSTR構(gòu)造函數(shù)。沒有對(duì)LPCSTR 和LPCWSTR的構(gòu)造函數(shù)。在大多數(shù)情況下這不是一個(gè)問題,因?yàn)椴还茉鯓幽愕淖址芸赡苁荓PCTSTRs,但這是一個(gè)需要意識(shí)到的問題。COleVariant還有一個(gè)接收CString參數(shù)的構(gòu)造函數(shù)。
// Constructing
CString s1 = _T("tchar string");
COleVariant v1 = _T("Bob"); // construct from an LPCTSTR
COleVariant v2 = s1; // copy from a CString
像CComVariant一樣,你必須直接訪問VARIANT的成員。如果需要把VARIANT轉(zhuǎn)換成一個(gè)字符串,你應(yīng)該使用ChangeType()方法。然而,COleVariant::ChangeType()如果失敗會(huì)拋出異常,而不是返回一個(gè)表示失敗的HRESULT代碼。
// Extracting data
COleVariant v3 = ...; // fill in v3 from somewhere
BSTR 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完全一樣,所以你可以參考上面關(guān)于MFC的 CString的介紹。
CLR 和 VC 7 類
System::String是用來處理字符串的.NET類。在內(nèi)部,一個(gè)String對(duì)象包含一個(gè)不可改變的字符串序列。任何對(duì)String對(duì)象的操作實(shí)際上都是返回了一個(gè)新的String對(duì)象,因?yàn)樵嫉膶?duì)象是不可改變的。String的一個(gè)特性是如果你有不止一個(gè)String對(duì)象包含相同的字符序列,它們實(shí)際上是指向相同的對(duì)象的。相對(duì)于C++的使用擴(kuò)展是增加了一個(gè)新的字符串常量前綴S,S用來代表一個(gè)受控的字符串常量(a managed string literal)。
// Constructing
String* ms = S"This is a nice managed string";
你可以傳遞一個(gè)非受控的字符串來創(chuàng)建一個(gè)String對(duì)象,但是樣會(huì)比使用受控字符串來創(chuàng)建String對(duì)象造成效率的微小損失。這是因?yàn)樗幸許作為前綴的相同的字符串實(shí)例都代表同樣的對(duì)象,但這對(duì)非受控對(duì)象是不適用的。下面的代碼清楚地闡明了這一點(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
正確的比較可能沒有使用S前綴的字符串的方法是使用String::CompareTo()
Console::WriteLine ( ms1->CompareTo(ms2) );
Console::WriteLine ( ms1->CompareTo(ms3) );
上面的兩行代碼都會(huì)打印0,0表示兩個(gè)字符串相等。 String和MFC 7 CString之間的轉(zhuǎn)換是很容易的。CString有一個(gè)向LPCTSTR的轉(zhuǎn)換操作,而String有兩個(gè)接收char* 和 wchar_t*的構(gòu)造函數(shù),因此你可以把一個(gè)CString變量直接傳給一個(gè)String的構(gòu)造函數(shù)。
CString s1 ( "hello world" );
String* s2 ( s1 ); // copy from a CString
反方向的轉(zhuǎn)換也很類似
String* s1 = S"Three cats";
CString s2 ( s1 );
這也許會(huì)使你感到一點(diǎn)迷惑,但是它確實(shí)是起作用的。因?yàn)閺腣S.NET 開始,CString 有了一個(gè)接收String 對(duì)象的構(gòu)造函數(shù)。
CStringT ( System::String* pString );
對(duì)于一些快速操作,你可能想訪問底層的字符串:
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ì)在我們正在管理它的內(nèi)容的時(shí)候移動(dòng)了它。
在 printf-style 格式函數(shù)中使用字符串類
當(dāng)你在printf()或者類似的函數(shù)中使用字符串封裝類時(shí)你必須十分小心。這些函數(shù)包括sprintf()和它的變體,還有TRACE和ATLTRACE宏。因?yàn)檫@些函數(shù)沒有對(duì)添加的參數(shù)的類型檢查,你必須小心,只能傳給它們C語言風(fēng)格的字符串指針,而不是一個(gè)完整的字符串類。 例如,要把一個(gè)_bstr_t 字符串傳給ATLTRACE(),你必須使用顯式轉(zhuǎn)換(LPCSTR) 或者(LPCWSTR):
_bstr_t bs = L"Bob!";
ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine);
如果你忘了使用轉(zhuǎn)換符而把整個(gè)_bstr_t對(duì)象傳給了函數(shù),將會(huì)顯示一些毫無意義的輸出,因?yàn)開bstr_t保存的內(nèi)部數(shù)據(jù)會(huì)全部被輸出。
所有類的總結(jié)
兩個(gè)字符串類之間進(jìn)行轉(zhuǎn)換的常用方式是:先把源字符串轉(zhuǎn)換成一個(gè)C語言風(fēng)格的字符串指針,然后把這個(gè)指針傳遞給目的類型的構(gòu)造函數(shù)。下面這張表顯示了怎樣把一個(gè)字符串轉(zhuǎn)換成一個(gè)C語言風(fēng)格的字符串指針以及哪些類具有接收C語言風(fēng)格的字符串指針的構(gòu)造函數(shù)。
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 提供了向非常量指針的轉(zhuǎn)換操作符,修改底層的緩沖區(qū)也會(huì)已引起GPF如果你溢出了緩沖區(qū)或者造成內(nèi)存泄漏。
2、_bstr_t 在內(nèi)部用一個(gè) wchar_t* 來保存 BSTR,所以你可以使用 const wchar_t* 來訪問BSTR。這是一個(gè)實(shí)現(xiàn)細(xì)節(jié),你可以小心的使用它,將來這個(gè)細(xì)節(jié)也許會(huì)改變。
3、如果數(shù)據(jù)不能轉(zhuǎn)換成BSTR會(huì)拋出一個(gè)異常。
4、使用 ChangeType(),然后訪問 VARIANT 的 bstrVal 成員。在MFC中,如果數(shù)據(jù)轉(zhuǎn)換不成功將會(huì)拋出異常。
5、這里沒有轉(zhuǎn)換 BSTR 函數(shù),然而 AllocSysString() 返回一個(gè)新的BSTR。
6、使用 GetBuffer() 方法,你可以暫時(shí)地得到一個(gè)非常量的TCHAR指針。
|
|