当前位置 博文首页 > Linruin的博客:【转】C++字符串完全指引之二 —&m
C++字符串完全指引之二 —— 字符串封装类
原著:Michael Dunn
原文出处:CodeProject:The Complete Guide to C++ Strings, Part II
因为C语言风格的字符串容易出错且不易管理,黑客们甚至利用可能存在的缓冲区溢出bug把C语言风格的字符串作为攻击目标,所以出现了很多字符串封装类。不幸的是,在某些场合下我们不知道该使用哪个字符串类,也不知道怎样把一个C风格的字符串转换成一个字符串封装类。
这篇文章将介绍所有在Win32 API, MFC, STL, WTL 和 Visual C++ 运行库中出现的字符串类型。我将描述每一个类的用法,告诉大家怎样创建每一个类的对象以及怎样把一个类转换成其他类。受控字符串和Visual C++ 7中的类两部分是Nish完成的。
为了更好的从这篇文章中受益,你必须要明白不同的字符类型和编码,这些内容我在第一部分中介绍过。
Rule #1 of string classes
使用cast来实现类型转换是不好的做法,除非有文档明确指出这种转换可以使用。
促使我写这两篇文章的原因是字符串类型转换中经常遇到的一些问题。当我们使用cast把字符串从类型X转换到类型Z的时候,我们不知道为什么代码不能正常工作。各种各样的字符串类型,尤其是BSTR,几乎没有在任何一个地方的文档中被明确的指出可以用cast来实现类型转换。所以我想一些人可能会使用 cast来实现类型转换并希望这种转换能够正常工作。
除非源字符串是一个被明确指明支持转换操作符的字符串包装类,否则cast不对字符串做任何转换。对常量字符串使用cast不会起到任何作用,所以下面的代码:
void SomeFunc ( LPCWSTR widestr );
main()
{
??? SomeFunc ( (LPCWSTR) "C://foo.txt" );? // WRONG!
}??
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字符。
这里给出你将会看到的一些OLECHAR相关的typedefs:
还有两个用于包围字符串和字符常量的宏定义,它们可以使同样的代码被用于MBCS和Unicode builds :
Type
Meaning
?
在文档或例程中,你还会看到好多_T的变体。有四个等价的宏定义,它们是TEXT, _TEXT, __TEXT和__T,它们都起同样的做用。
很多自动化和COM接口使用BSTR来定义字符串。BSTRs中有几个"陷阱",所以这里我用单独的部分来说明它。
BSTR 是 Pascal-style 字符串(字符串长度被明确指出)和C-style字符串(字符串的长度要通过寻找结束符来计算)的混合产物。一个BSTR是一个Unicode字符串,它的长度是预先考虑的,并且它还有一个0字符作为结束标记。下面是一个BSTR的示例:
?
注意字符串的长度是如何被加到字符串数据中的。长度是DWORD类型的,保存了字符串中包含的字节数,但不包括结束标记。在这个例子中,"Bob"包含 3个Unicode字符(不包括结束符),总共6个字节。字符串的长度被预先存储好,以便当一个BSTR在进程或者计算机之间被传递时,COM库知道多少数据需要传送。(另一方面,一个BSTR能够存储任意数据块,而不仅仅是字符,它还可以包含嵌入在数据中的0字符。然而,由于这篇文章的目的,我将不考虑那些情况)。
在 C++ 中,一个 BSTR 实际上就是一个指向字符串中第一个字符的指针。它的定义如下:
BSTR bstr = NULL;
bstr = SysAllocString ( L"Hi Bob!" );
if ( NULL == bstr )
// out of memory error
// Use bstr here...
SysFreeString ( 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 );?????
// 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
// 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??
// Sample interface:
struct IStuff : public IUnknown
{
??? // Boilerplate COM stuff omitted...
??? STDMETHOD(SetText)(BSTR bsText);
??? STDMETHOD(GetText)(BSTR* pbsText);
};?????
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??
// 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 );??
std::list< CAdapt<CComBSTR> > bstr_list;
CAdapt提供容器所需要的操作符,但这些操作符对你的代码是透明的。你可以把一个bstr_list当作一个CComBSTR的list来使用。
CComVariant
CComVariant是VARIANT的封装类。然而,不像_variant_t,在CComVariant中VARIANT没有被隐藏。事实上你需要直接访问VARIANT的成员。CComVariant提供了很多构造函数来对VARIANT能够包含的多种类型进行处理。这里,我将只介绍和字符串相关的操作。
// 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????
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的转换宏。
ATL:转换宏是各种字符编码之间进行转换的一种很方便的方式,在函数调用时,它们显得非常有用。ATL转换宏的名称是根据下面的模式来命名的[源类型]2[新类型]或者[源类型]2C[新类型]。据有第二种形式的名字的宏的转换结果是常量指针(对应名字中的"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()宏把一个Unicode字符串转换成一个MBCS字符串。T2CW()宏把一个TCHAR字符串转转成一个Unicode字符串常量。
为了使用这些宏,需要先包含atlconv.h头文件。你甚至可以在非ATL工程中包含这个头文件来使用其中定义的宏,因为这个头文件独立于ATL中的其他部分,不需要一个_Module全局变量。当你在一个函数中使用转换宏时,需要把USES_CONVERSION宏放在函数的开头。它定义了转换宏所需的一些局部变量。
当转换的目的类型是除了BSTR以外的其他类型时,被转换的字符串是存在栈中的。所以,如果你想让字符串的生命周期比当前的函数长,你需要把这个字符串拷贝到其他的字符串类中。当目的类型是BSTR时,内存不会自动被释放,你必须把返回值赋给一个BSTR变量或者一个BSTR封装类以避免内存泄漏。
下面是一些各种转换宏的使用例子:
// 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 );???
// 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 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??
// Converting to BSTR
CString s5 = "Bob!";
BSTR bs1 = NULL, bs2 = NULL;
bs1 = s5.AllocSysString();
s5.SetSysString ( &bs2 );
SysFreeString ( bs1 );
SysFreeString ( bs2 );?
// Constructing
CString s1 = _T("tchar string");
COleVariant v1 = _T("Bob"); // construct from an LPCTSTR
COleVariant v2 = s1; // copy from a CString?????
// 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 );?????
CString
WTL的CString的行为和MFC的 CString完全一样,所以你可以参考上面关于MFC的 CString的介绍。
// Constructing
String* ms = S"This is a nice managed string";
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????
Console::WriteLine ( ms1->CompareTo(ms2) );
Console::WriteLine ( ms1->CompareTo(ms3) );
CString s1 ( "hello world" );
String* s2 ( s1 );? // copy from a CString???
String* s1 = S"Three cats";
CString s2 ( s1 );???
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语言风格的字符串指针,然后把这个指针传递给目的类型的构造函数。下面这张表显示了怎样把一个字符串转换成一个C语言风格的字符串指针以及哪些类具有接收C语言风格的字符串指针的构造函数。