原著:Michael Dunn 作者:Chengjie Sun 原文出处:CodeProject:The Complete Guide to C++ Strings, Part II Rule #1 of string classes 使用cast来实现类型转换是不好的做法,除非有文档明确指出这种转换可以使用。 void SomeFunc ( LPCWSTR widestr ); main() { SomeFunc ( (LPCWSTR) "C:\\foo.txt" ); // WRONG! } 肯定会失败。它可以被编译,因为cast操作会撤消编译器的类型检查。但是,编译可以通过并不能说明代码是正确的。 C-style strings and typedefs 正如我在第一部分中提到的,windows APIs 是用TCHARs来定义的,在编译时,它可以根据你是否定义_MBCS或者_UNICODE被编译成MBCS或者Unicode字符。你可以参看第一部分 中对TCHAR的完整描述,这里为了方便,我列出了字符的typedefs
一个增加的字符类型是OLETYPE。它表示自动化接口(如word提供的可以使你操作文档的接口)中使用的字符类型。这种类型一 般被定义成wchar_t,然而如果你定义了OLE2ANSI预处理标记,OLECHAR将会被定义成char类型。我知道现在已经没有理由定义 OLE2ANSI(从MFC3以后,微软已经不使用它了),所以从现在起我将把OLECHAR当作Unicode字符。
还有两个用于包围字符串和字符常量的宏定义,它们可以使同样的代码被用于MBCS和Unicode builds :
在文档或例程中,你还会看到好多_T的变体。有四个等价的宏定义,它们是TEXT, _TEXT, __TEXT和__T,它们都起同样的做用。
注意字符串的长度是如何被加到字符串数据中的。长度是DWORD类型的,保存了字符串中包含的字节数,但不包括结束标记。在这个例 子中,"Bob"包含3个Unicode字符(不包括结束符),总共6个字节。字符串的长度被预先存储好,以便当一个BSTR在进程或者计算机之间被传递 时,COM库知道多少数据需要传送。(另一方面,一个BSTR能够存储任意数据块,而不仅仅是字符,它还可以包含嵌入在数据中的0字符。然而,由于这篇文 章的目的,我将不考虑那些情况)。 BSTR bstr = NULL; bstr = SysAllocString ( L"Hi Bob!" ); if ( NULL == bstr ) // out of memory error // Use bstr here... SysFreeString ( bstr ); 自然的,各种各样的BSTR封装类为你实现内存管理。 // 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*之间的转换操作符。这是一个值得怀疑的设计,因为即使它们是非常量字符串指针,你也一定不能使用这些指针去修改它们指向的缓冲区的内容,因为那将破坏内部的BSTR结构。 // 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 注意: // 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,一个basic_string变量不能在字符集之间直接转换。然而,你可以传递由c_str()返回的指针给另外一个类的构造函数(如果这个类的构造函数接受这种字符类型)。例如: // 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 类 // Sample interface: struct IStuff : public IUnknown { // Boilerplate COM stuff omitted... STDMETHOD(SetText)(BSTR bsText); STDMETHOD(GetText)(BSTR* pbsText); }; CComBSTR有一个操作符--BSTR方法,所以它能直接被传给SetText()函数。还有另外一个操作--&,这个操作符返回一个 BSTR*。所以,你可以对一个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 line CComBSTR有和_bstr_t相似的构造函数,然而却没有内置的向MBCS字符串转换的函数。因此,你需要使用一个ATL转换宏。 // 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 ); 注意在上个例子中使用了Detach()方法。调用这个方法后,CComBSTR对象不再管理它的BSTR字符串或者说它对应的内存。这就是bstr4需要调用SysFreeString()的原因。 std::list< CAdapt<CComBSTR> > bstr_list; CAdapt提供容器所需要的操作符,但这些操作符对你的代码是透明的。你可以把一个bstr_list当作一个CComBSTR的list来使用。 // 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,这里没有提供针对VARIANT包含的各种类型的转换操作符。正如上面介绍的,你必须直接访问VARIANT的成员并且确 保这个VARIANT变量保存着你期望的类型。如果你需要把一个CComVariant类型的数据转换成一个BSTR类型的数据,你可以调用 ChangeType()方法。 CComVariant v4 = ... // Init v4 from somewhere CComBSTR bs3;
if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) )) bs3 = v4.bstrVal; 像_variant_t一样,CComVariant也没有提供向MBCS字符串转换的转换操作。你需要创建一个_bstr_t类型的中间变量,使用提供从Unicode到MBCS转换的另一个字符串类,或者使用一个ATL的转换宏。 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()宏把一个Unicode字符串转换成一个MBCS字符串。T2CW()宏把一个TCHAR字符串转转成一个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 } 正如你所看见的,当你有一个和函数所需的参数类型不同的字符串时,使用这些转换宏是非常方便的。 // 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 ); 你可以从你的字符串表中装载一个字符串,CString的一个构造函数和LoadString()函数可以完成它。Format()方法能够从字符串表中随意的读取一个具有一定格式的字符串。 // 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, ... ); 第一个构造函数看起来有点奇怪,但是这实际上是文档说明的装入一个字符串的方法。 注意,对一个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()返回的是指向字符串当前内容的指针。 // Converting to BSTR CString s5 = "Bob!"; BSTR bs1 = NULL, bs2 = NULL; bs1 = s5.AllocSysString(); s5.SetSysString ( &bs2 ); SysFreeString ( bs1 ); SysFreeString ( bs2 ); COleVariant // Constructing CString s1 = _T("tchar string"); COleVariant v1 = _T("Bob"); // construct from an LPCTSTR COleVariant v2 = s1; // copy from a CString 像CComVariant一样,你必须直接访问VARIANT的成员。如果需要把VARIANT转换成一个字符串,你应该使用ChangeType()方 法。然而,COleVariant::ChangeType()如果失败会抛出异常,而不是返回一个表示失败的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 );
// Constructing String* 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* ,我们需要固定它,否则垃圾收集器或许会在我们正在管理它的内容的时候移动了它。 _bstr_t bs = L"Bob!"; ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine); 如果你忘了使用转换符而把整个_bstr_t对象传给了函数,将会显示一些毫无意义的输出,因为_bstr_t保存的内部数据会全部被输出。
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
作者简介 |
C++字符串完全指引之二 —— 字符串封装类
最新推荐文章于 2024-10-08 18:38:07 发布