C++字符串完全指引之二:字符串封装类
副标题#e#
引言
因为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
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*) |
#p#副标题#e#
一个增加的字符范例是OLETYPE。它暗示自动化接口(如word提供的可以使你操纵文档的接口)中利用的字符范例。这种范例一般被界说成wchar_t,然而假如你界说了OLE2ANSI预处理惩罚标志,OLECHAR将会被界说成char范例。我知道此刻已经没有来由界说OLE2ANSI(从MFC3今后,微软已经不利用它了),所以以后刻起我将把OLECHAR看成Unicode字符。
这里给出你将会看到的一些OLECHAR相关的typedefs:
Type | Meaning |
OLECHAR | Unicode character (wchar_t) |
LPOLESTR | string of OLECHAR (OLECHAR*) |
LPCOLESTR | constant string of OLECHAR (const OLECHAR*) |
尚有两个用于困绕字符串和字符常量的宏界说,它们可以使同样的代码被用于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中有几个"陷阱",所以这里我用单独的部门来说明它。
#p#分页标题#e#
BSTR 是 Pascal-style 字符串(字符串长度被明晰指出)和C-style字符串(字符串的长度要通过寻找竣事符来计较)的殽杂产品。一个BSTR是一个Unicode字符串,它的长度是预先思量的,而且它尚有一个0字符作为竣事标志。下面是一个BSTR的示例:
06 00 00 00 | 42 00 | 6F 00 | 62 00 | 00 00 |
–length– | B | o | b | EOS |
留意字符串的长度是如何被加到字符串数据中的。长度是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。它提供各类结构函数和操纵符来会见底层的C语言气势气魄的字符串。然而,_bstr_t却没有会见BSTR自己的操纵符,所以一个_bstr_t范例的字符串不能被作为输出参数传给一个COM要领。假如你需要一个BSTR*参数,利用ATL类CComBSTR是较量容易的方法。
一个_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 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布局。
_variant_t
_variant_t是一个对VARIANT的完整封装,它提供许多结构函数和转换函数来操纵一个VARIANT大概包括的大量的数据范例。这里,我将只先容与字符串有关的操纵。
#p#分页标题#e#
// 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转换宏。
不像_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的范例,可是你可以利用下面列出的代码来实现。
// 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 类
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
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()的原因。
#p#分页标题#e#
做一个增补说明:重载的&操纵符意味着在一些STL容器中你不能直接利用CComBSTR变量,好比list。容器要求&操纵符返回一个指向容器包括的类的指针,可是对CComBSTR变量利用&操纵符返回的是BSTR*,而不是CComBSTR*。然而,有一个ATL类可以办理这个问题,这个类是CAdapt。譬喻,你可以这样声明一个CComBSTR的list:
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
不像_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的转换宏。
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
}
正如你所瞥见的,当你有一个和函数所需的参数范例差异的字符串时,利用这些转换宏长短常利便的。
MFC类
CString
#p#分页标题#e#
因为一个MFC CString类的工具包括TCHAR范例的字符,所以确切的字符范例取决于你所界说的预处理惩罚标记。概略来说,CString 很像STL string,这意味着你必需把它当成不透明的工具,只能利用CString提供的要领来修改CString工具。CString有一个string所不具备的利益:CString具有吸收MBCS和Unicode两种字符串的结构函数,它尚有一个LPCTSTR转换符,所以你可以把CString工具直接传给一个吸收LPCTSTR的函数而不需要挪用c_str()函数。
// 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()返回的是指向字符串当前内容的指针。
上面划线的语句可以被编译,在这种环境下,甚至可以正常起浸染。但这并不料味着这行代码是正确的。通过利用很是量转换,你已经粉碎了面向工具的封装,并对CString的内部实现作了某些假定。假如你有这样的转换习惯,你终将会陷入代码瓦解的田地。你会想代码为什么不能正常事情了,因为你处处都利用同样的代码而那些代码看起来是正确的。
你知道人们老是诉苦此刻的软件的bug是何等的多吗?软件中的bug是因为措施员写了不正确的代码。莫非你真的想写一些你知道是错误的代码来为所有的软件都满是bug这种认识做孝敬吗?花些时间来进修利用CString的正确要领让你的代码在任何时间都正常事情把。
CString 有两个函数来从一个 CString 建设一个 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的函数。然而,不像CComVariant,COleVariant只有一个LPCTSTR结构函数。没有对LPCSTR 和LPCWSTR的结构函数。在大大都环境下这不是一个问题,因为不管奈何你的字符串很大概是LPCTSTRs,但这是一个需要意识到的问题。COleVariant尚有一个吸收CString参数的结构函数。
#p#分页标题#e#
// 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 );
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)。
// 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* ,我们需要牢靠它,不然垃圾收集器或者会在我们正在打点它的内容的时候移动了它。
在 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生存的内部数据会全部被输出。
所有类的总结
两个字符串类之间举办转换的常用方法是:先把源字符串转换成一个C语言气势气魄的字符串指针,然后把这个指针通报给目标范例的结构函数。下面这张表显示了奈何把一个字符串转换成一个C语言气势气魄的字符串指针以及哪些类具有吸收C语言气势气魄的字符串指针的结构函数。
#p#分页标题#e#
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 提供了向很是量指针的转换操纵符,修改底层的缓冲区也会已引起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指针。 |