全面掌握 C++字符串封装类
因为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!}
肯定会失败。它可以被编译,因为cast 操作会撤消编译器的类型检查。但是,编译可以通
过并不能说明代码是正确的。
在下面的例子中,我将会指明cast 在什么时候使用是合法的。
C-style strings and typedefs
正如我在第一部分中提到的,windows APIs 是用 TCHARs 来定义的,在编译时,它可
以根据你是否定义_MBCS或者_UNICODE 被编译成MBCS 或者 Unicode 字符。你可以参看第一
部分 中对TCHAR 的完整描述,这里为了方便,我列出了字符的typedefs
TypeMeaning
WCHARUnicode character (wchar_t)
TCHarmBCS or Unicode character, depending on preprocessor settings
LPSTR string of char (char*)
LPCSTRconstant 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*)
一个增加的字符类型是OLETYPE。它表示自动化接口(如word 提供的可以使你操作文
档的接口)中使用的字符类型。这种类型一般被定义成 wchar_t,然而如果你定义了
OLE2ANSI预处理标记,OLECHAR 将会被定义成char 类型。我知道现在已经没有理由定义
OLE2ANSI(从 MFC3以后,微软已经不使用它了),所以从现在起我将把 OLECHAR当作 Unicode
字符。
这里给出你将会看到的一些OLECHAR 相关的typedefs:
TypeMeaning
OLECHAR Unicode character (wchar_t)
LPOLESTR string of OLECHAR (OLECHAR*)
LPCOLESTR constant string of OLECHAR (const OLECHAR*) [Page]
还有两个用于包围字符串和字符常量的宏定义,它们可以使同样的代码被用于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.
在文档或例程中,你还会看到好多_T的变体。有四个等价的宏定义,它们是TEXT, _TEXT,
__TEXT和__T,它们都起同样的做用。
COM 中的字符串 —— BSTR 和 VARIANT
很多自动化和COM 接口使用BSTR 来定义字符串。BSTRs中有几个\"陷阱\",所以这里
我用单独的部分来说明它。
BSTR 是 Pascal-style 字符串(字符串长度被明确指出)和C-style 字符串(字符串
的长度要通过寻找结束符来计算)的混合产物。一个BSTR 是一个Unicode 字符串,它的长
度是预先考虑的,并且它还有一个0字符作为结束标记。下面是一个BSTR 的示例:
06 00 00 0042 006F 0062 0000 00
--length--BobEOS
注意字符串的长度是如何被加到字符串数据中的。长度是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 );
自然的,各种各样的BSTR 封装类为你实现内存管理。
另外一个用在自动化接口中的变量类型是VARIANT。它被用来在无类型(typeless)语
言,如Jscript 和 VBScript,来传递数 据。一个 VARIANT可能含有很多不同类型的数据,
例如long 和 IDispatch*。当一个 VARIANT包含一个字符串,字符串被存成一个 BSTR。当
我后面讲到VARIANT 封装类时,我会对VARIANT 多些介绍。
字符串封装类
到目前为止,我已经介绍了各种各样的字符串。下面,我将说明封装类。对于每个封装
类,我将展示怎样创建一个对象及怎样把它转换成一个C 语言风格的字符 串指针。C语言
风格的字符串指针对于API 的调用,或者创建一个不同的字符串类对象经常是必需的。我不
会介绍字符串类提供的其他操作,比如排序和比较。
重复一遍,除非你确切的明白结果代码将会做什么,否则不要盲目地使用cast 来实现
类型转换。
CRT提供的类
_bstr_t
_bstr_t是一个对 BSTR的完整封装类,实际上它隐藏了底层的 BSTR。它提供各种构造
函数
excel方差函数excelsd函数已知函数 2 f x m x mx m 2 1 4 2拉格朗日函数pdf函数公式下载
和操作符来访问底层的C 语言风格的字符串。然而, _bstr_t 却没有访问BSTR 本身的
操作符,所以一个_bstr_t类型的字符串不能被作为输出参数传给一个 COM方法。如果你需
要一个BSTR*参 数,使用 ATL类 CComBSTR是比较容易的方式。
[NextPage]
一个_bstr_t字符串能够传给一个接收参数类型为 BSTR的函数,只是因为下列3个条件
同时满足。首先,_bstr_t有一个向 wchar_t* 转换的转换函数;其次,对编译器而言,因
为BSTR 的定义,wchar_t*和 BSTR 有同样的含义;第三,_bstr_t内部含有的 wchar_t*指 向
一片按BSTR 的形式存储数据的内存。所以,即使没有文档说明,_bstr_t可以转换成 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 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*之间的转换操作符。这是一个值得怀疑的
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
,因为
即使它们是非常量字符串指针,你也一定不能使用这些指针去修改它们指向的缓冲区的内
容,因为那将破坏内部的BSTR 结构。
_variant_t
_variant_t是一个对 VARIANT的完整封装,它提供很多构造函数和转换函数来操作一
个VARIANT 可能包含的大量的数据类型。这里,我将只介绍与字符串有关的操作。
// 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
注意:
如果类型转换不能被执行,_variant_t方法能够抛出异常,所以应该准备捕获
_com_error异常。
还需要注意的是:
没有从一个_variant_t变量到一个 MBCS字符串的直接转换。你需要创建一个临时的
_bstr_t变量,使用提供Unicode 到 MBCS 转换的另一个字符串类或者使用一个ATL 转换宏。
[Page]
不像_bstr_t,一个_variant_t变量可以被直接作为参数传递给一个 COM方法。
_variant_t
继承自VARIANT 类型,所以传递一个_variant_t来代替 VARIANT变量是 C++语言所允许
的。
STL 类
STL只有一个字符串类,basic_string。一个basic_string 管理一个以0做结束符的字
符串数组。字符的类型是 basic_string模般的参数。总的来说,一个 basic_string类型
的变量应该被当作不透明的对象。你可以得到一个指向内部缓冲区的只读指 针,但是任何
写操作必须使用basic_string 的操作符和方法。
basic_string有两个预定义的类型:包含char的string类型和包含wchar_t的wstring
类型。这里没有内置的包含TCHAR 的类型,但是你可以使用下面列出的代码来实现。
// Specializationstypedef basic_string tstring; // string of TCHARs //
Constructingstring str = \"char string\"; // construct from a LPCSTRwstring
wstr = L\"wide char string\"; // construct from a LPCWSTRtstring tstr = _T(\"TCHAR
string\"); // construct from a LPCTSTR // Extracting dataLPCSTR psz = str.c_str();
// read-only pointer to str’’s bufferLPCWSTR pwsz = wstr.c_str(); // read-only
pointer to wstr’’s bufferLPCTSTR 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 类
CComBSTR
CComBSTR 是 ATL 中的 BSTR 封装类,它在某些情况下比_bstr_t有用的多。最引人注
意的是CComBSTR 允许访问底层的BSTR,这意味着你可以传递一个CComBSTR 对象 给COM 的
方法。CComBSTR 对象能够替你自动的管理BSTR 的内存。例如,假设你想调用下面这个接口
的方法:
// 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
[NextPage]
CComBSTR有和_bstr_t 相似的构造函数,然而却没有内置的向MBCS 字符串转换的函数。因
此,你需要使用一个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 );
注意在上个例子中使用了Detach()方法。调用这个方法后,CComBSTR对象不再管理它的BSTR
字符串或者说它对应的内存。这就是bstr4需要调用 SysFreeString()的原因。
做一个补充说明:重载的&操作符意味着在一些STL 容器中你不能直接使用CComBSTR
变量,比如list。容器要求&操作符返回 一个指向容器包含的类的指针,但是对 CComBSTR
变量使用&操作符返回的是BSTR*,而不是 CComBSTR*。然而,有一个ATL 类可以 解决这个
问题,这个类是CAdapt。例如,你可以这样声明一个 CComBSTR的 list:
std::list< CAdapt
> bstr_list;
CAdapt提供容器所需要的操作符,但这些操作符对你的代码是透明的。你可以把一个
bstr_list当作一个 CComBSTR的 list来使用。
CComVariant
CComVariant是 VARIANT的封装类。然而,不像_variant_t,在 CComVariant中 VARIANT
没有被隐藏。事实上你 需要直接访问VARIANT 的成员。CComVariant提供了很多构造函数
来对VARIANT 能够包含的多种类型进行处理。这里,我将只介绍和字符串 相关的操作。
// 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,这里没有提供针对VARIANT 包含的各种类型的转换操作符。正如上面介绍
的,你必须直接访问VARIANT 的成员并且确 保这个VARIANT 变量保存着你期望的类型。如
果你需要把一个CComVariant 类型的数据转换成一个BSTR 类型的数据,你可以调用
ChangeType()方法。 [Page]
CComVariant v4 = ... // Init v4 from somewhereCComBSTR bs3; if
( SUCCEEDED( v4.ChangeType ( VT_BSTR ) )) bs3 = v4.bstrVal;
像_variant_t一样,CComVariant 也没有提供向MBCS 字符串转换的转换操作。你需要创建
一个_bstr_t类型的中间变量,使用提供从 Unicode到 MBCS转换的另一个字符串类,或者
使用一个ATL 的转换宏。
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
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}
[NextPage]
正如你所看见的,当你有一个和函数所需的参数类型不同的字符串时,使用这些转换宏是非
常方便的。
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()返回的是指向字符串当前内容的指针。 [Page]
上面划线的语句可以被编译,在这种情况下,甚至可以正常起作用。但这并不意味着
这行代码是正确的。通过使用非常量转换,你已经破坏了面向对象的封装,并对 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)。
[NextPage]
// 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保存的内部数据会全部被输出。
所有类的总结 [Page]
两个字符串类之间进行转换的常用方式是:先把源字符串转换成一个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_tBSTRyes cast1yes castyes cast1yes castyes2yesyes
_variant_tBSTRnononocast to
_bstr_t3cast to
_bstr_t3yesyes
stringMBCSnoyes c_str() methodnononoyesno
wstringUnicodenononoyes c_str() methodnonoyes
CComBSTRBSTRnononoyes cast to BSTRyes castyesyes
CComVariantBSTRnononoyes4yes4yesyes
CString TCHARno6in MBCS
builds, castno6in Unicode
builds, castno5yesyes
COleVariantBSTRnononoyes4yes4in MBCS
buildsin 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 指针。
作者简介
Michael Dunn:
Michael Dunn居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住
在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在UCLA 获得数学学
士学位,随后在Symantec 公司做QA 工程师,在 Norton AntiVirus 组工作。他自学了
Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus的新界面。
Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发
了UltraBar,一个 IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重
打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在
洛杉矶和丹麦的 Odense 都设有办事处。
他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和
MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。
Nishant S(Nish):
Nish是来自印度 Trivandrum,的 Microsoft Visual C++ MVP。他从1990年开始编码。
现在,Nish为作为合同雇员在家里为 CodeProject 工作。
他还写了一部浪漫戏剧《Summer Love and Some more Cricket》和一本编程书籍
《Extending MFC applications with the .NET Framework》。他还管理者MVP 的一个网站
http://www.voidnish.com/ 。在这个网站上,你可以看到他的很多关于编程方面的思想和
文章。 [Page]
Nish 还
计划
项目进度计划表范例计划下载计划下载计划下载课程教学计划下载
好了旅游,他希望自一生中能够到达地球上尽可能多的地方。