前言 字符串的表现形式各异,象TCHAR,std::string,BSTR等等,有时还会见到怪怪的用_tcs起头的宏。这个指南的目的就是说明各种字符串类型及其用途,并说明如何在必要时进行类型的相互转换。 在指南的第一部分,介绍三种字符编码格式。理解编码的工作原理是致为重要的。即使你已经知道字符串是一个字符的数组这样的概念,也请阅读本文,它会让你明白各种字符串类之间的关系。 指南的第二部分,将阐述各个字符串类,什么时候使用哪种字符串类,及其相互转换。 字符串基础 - ASCII, DBCS, Unicode 第一种编码方式是单字节字符集,称之为SBCS,它的所有字符都只有一个字节的长度。ASCII码就是SBCS。SBCS字符串由一个零字节结尾。 第二种编码方式是多字节字符集,称之为MBCS,它包含的字符中有单字节长的字符,也有多字节长的字符。Windows用到的MBCS只有二种字符类型,单字节字符和双字节字符。因此Windows中用得最多的字符是双字节字符集,即DBCS,通常用它来代替MBCS。 在DBCS编码中,用一些保留值来指明该字符属于双字节字符。例如,Shift-JIS(通用日语)编码中,值0x81-0x9F 和 0xE0-0xFC 的意思是:“这是一个双字节字符,下一个字节是这个字符的一部分”。这样的值通常称为前导字节(lead byte),总是大于0x7F。前导字节后面是跟随字节(trail byte)。DBCS的跟随字节可以是任何非零值。与SBCS一样,DBCS字符串也由一个零字节结尾。 第三种编码方式是Unicode。Unicode编码标准中的所有字符都是双字节长。有时也将Unicode称为宽字符集(wide characters),因为它的字符比单字节字符更宽(使用更多内存)。注意,Unicode不是MBCS - 区别在于MBCS编码中的字符长度是不同的。Unicode字符串用二个零字节字符结尾(一个宽字符的零值编码)。 单字节字符集是拉丁字母,重音文字,用ASCII标准定义,用于DOS操作系统。双字节字符集用于东亚和中东语言。Unicode用于COM和Windows NT内部。 读者都很熟悉单字节字符集,它的数据类型是char。双字节字符集也使用char数据类型(双字节字符集中的许多古怪处之一)。Unicode字符集用wchar_t数据类型。Unicode字符串用L前缀起头,如: wchar_t wch = L'1'; // 2 个字节, 0x0031 wchar_t* wsz = L"Hello"; // 12 个字节, 6 个宽字符 字符串的存储
Unicode编码中,L"Bob"的存储格式为:
用0x0000 (Unicode的零编码)结束字符串。 DBCS 看上去有点象SBCS。以后我们会看到在串处理和指针使用上是有微妙差别的。字符串"日本语" (nihongo) 的存储格式如下(用LB和TB分别表示前导字节和跟随字节):
注意,"ni"的值不是WORD值0xFA93。值93和FA顺序组合编码为字符"ni"。(在高位优先CPU中,存放顺序正如上所述)。 字符串处理函数 微软在C运行库(CRT)中加入了对DBCS字符串的支持。对应于strxxx()函数,DBCS使用_mbsxxx()函数。在处理DBCS字符串(如日语,中文,或其它DBCS)时,就要用_mbsxxx()函数。这些函数也能用于处理SBCS字符串(因为DBCS字符串可能就只含有单字节字符)。 现在用一个示例来说明字符串处理函数的不同。如有Unicode字符串L"Bob":
x86 CPU的排列顺序是低位优先(little-endian)的,值0x0042的存储顺序为42 00。这时如用strlen()函数求字符串的长度就发生问题。函数找到第一个字节42,然后是00,意味着字符串结尾,于是返回1。反之,用wcslen()函数求"Bob"的长度更糟糕。wcslen()首先找到0x6F42,然后是0x0062,以后就在内存缓冲内不断地寻找00 00直至发生一般性保护错(GPF)。 strxxx()及其对应的_mbsxxx()究竟是如何运作的?二者之间的不同是非常重要的,直接影响到正确遍历DBCS字符串的方法。下面先介绍字符串遍历,然后再回来讨论strxxx()和 _mbsxxx()。
字符串遍历 但对于DBCS字符串就不能这样了。用指针访问DBCS字符串有二个原则,打破这二个原则就会造成错误。 1. 不可使用 ++ 算子,除非每次都检查是否为前导字节。 2. 绝不可使用 -- 算子来向后遍历。 先说明原则2,因为很容易找到一个非人为的示例。假设,有一个配制文件,程序启动时要从安装路径读取该文件,如:C:\Program Files\MyCoolApp\config.bin。文件本身是正常的。 假设用以下代码来配制文件名: bool GetConfigFileName ( char* pszName, size_t nBuffSize )
这时用上面的GetConfigFileName()函数来检查文件路径末尾是否含有反斜线就会出错,得到错误的文件名。 错在哪里?注意上面的二个十六进制值0x5C(蓝色)。前面的0x5C是字符"\",后面则是字符值83 5C,代表字符"ソ"。可是函数把它误认为反斜线了。 正确的方法是用DBCS函数将指针指向恰当的字符位置,如下所示: bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
现在可以想像到第一原则了。例如,要遍历字符串寻找字符":",如果不使用CharNext()函数而使用++算子,当跟随字节值恰好也是":"时就会出错。 与原则2相关的是数组下标的使用: 2a. 绝不可在字符串数组中使用递减下标。 出错原因与原则2相同。例如,设置指针pLastChar为: char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1]; 再谈strxxx() 与_mbsxxx() 最后提一下strxxx() 和 _mbsxxx() 函数族中的字符串长度测量函数,它们都返回字符串的字节数。如果字符串含有3个双字节字符,_mbslen()将返回6。而Unicode的函数返回的是wchar_ts的数量,如wcslen(L"Bob") 返回3 Win32 API中的MBCS 和 Unicode 编写Windows程序时,可以选择用MBCS或Unicode API接口函数。用VC AppWizards向导时,如果不修改预处理器设置,缺省使用的是MBCS函数。但是在API接口中没有SetWindowText()函数,该如何调用呢?实际上,在winuser.h头文件中做了以下定义: BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString ); #define SetWindowText SetWindowTextA 如果要将缺省应用接口改为Unicode,就到预处理设置的预处理标记中去掉 _MBCS标记,加入UNICODE 和 _UNICODE (二个标记都要加入,不同的头文件使用不同的标记)。不过,这时要处理普通字符串反而会遇到问题。如有代码: HWND hwnd = GetSomeWindowHandle(); HWND hwnd = GetSomeWindowHandle(); 第一种解决办法是使用宏定义: TCHAR的救火角色 TCHAR的宏定义如下: #ifdef UNICODE 对于Unicode字符串,还有个 _T() 宏,用于解决 L 前缀: #ifdef UNICODE TCHAR szNewText[] = _T("we love Bob!"); 不止strxxx()函数族中有TCHAR宏定义,其它一些函数中也有。例如,_stprintf (取代sprintf()和swprintf()),和 _tfopen (取代fopen() 和 _wfopen())。MSDN的全部宏定义在"Generic-Text Routine Mappings"栏目下。 String 和 TCHAR 类型定义 类型 何时使用TCHAR 和Unicode 在三种情况下要用到Unicode: 程序只运行于Windows NT。 NT允许使用超长文件名(长于MAX_PATH 定义的260),但只限于Unicode API使用。Unicode API的另外一个优点是程序能够自动处理输入的文字语言。用户可以混合输入英文,中文和日文作为文件名。不必使用其它代码来处理,都按照Unicode编码方式处理。 最后,作为Windows 9x的结局,微软似乎抛弃了MBCS API。例如,SetWindowTheme() 接口函数的二个参数只支持Unicode编码。使用Unicode编码省却了MBCS与Unicode之间的转换过程。 如果程序中还没有使用到Unicode编码,要坚持使用TCHAR和相应的宏。这样不但可以长期保持程序中DBCS编码的安全性,也利于将来扩展使用到Unicode编码。那时只要改变预处理中的设置即可! 各种字符串类(一) 前言 C语言的字符串容易出错,难以管理,并且往往是黑客到处寻找的目标。于是,出现了许多字符串包装类。可惜,人们并不很清楚什么情况下该用哪个类,也不清楚如何将C语言字符串转换到包装类。 本文涉及到Win32 API,MFC,STL,WTL和Visual C++运行库中使用到的所有的字符串类型。说明各个类的用法,如何构造对象,如何进行类转换等等。Nish为本文提供了Visual C++ 7的managed string 类的用法。 阅读本文之前,应完全理解本指南第一部分中阐述的字符类型和编码。 字符串类的首要原则: 不要随便使用类型强制转换,除非转换的类型是明确由文档规定的。 之所以撰写字符串指南这二篇文章,是因为常有人问到如何将X类型的字符串转换到Z类型。提问者使用了强制类型转换(cast),但不知道为什么不能转换成功。各种各样的字符串类型,特别是BSTR,在任何场合都不是三言二语可以讲清的。因此,我以为这些提问者是想让强制类型转换来处理一切。 除非明确规定了转换算子,不要将任何其它类型数据强制转换为string。一个字符串不能用强制类型转换到string类。例如: void SomeFunc ( LPCWSTR widestr );main(){ SomeFunc ( (LPCWSTR) "C:\\foo.txt" ); // 错!} 下面,我将指出什么时候用类型强制转换是合理的。 如指南的第一部分所述,Windows API定义了TCHAR术语。它可用于MBCS或Unicode编码字符,取决于预处理设置为_MBCS 或 _UNICODE标记。关于TCHAR的详细说明请阅指南的第一部分。为便于叙述,下面给出字符类型定义: Type 另外还有一个字符类型OLECHAR。这是一种对象链接与嵌入的数据类型(比如嵌入Word文档)。这个类型通常定义为wchar_t。如果将预处理设置定义为OLE2ANSI,OLECHAR将被定义为char类型。现在已经不再定义OLE2ANSI(它只在MFC 3以前版本中使用),所以我将OLECHAR作为Unicode字符处理。 下面是与OLECHAR相关的类型定义: Type 还有以下二个宏让相同的代码能够适用于MBCS和Unicode编码: Type 宏_T有几种形式,功能都相同。如: -- TEXT, _TEXT, __TEXT, 和 __T这四种宏的功能相同。
COM中的字符串 - BSTR 与 VARIANT 许多COM接口使用BSTR声明字符串。BSTR有一些缺陷,所以我在这里让它独立成章。 BSTR是Pascal类型字符串(字符串长度值显式地与数据存放在一起)和C类型字符串(字符串长度必须通过寻找到结尾零字符来计算)的混合型字符串。BSTR属于Unicode字符串,字符串中预置了字符串长度值,并且用一个零字符来结尾。下面是一个"Bob"的BSTR字符串:
注意,字符串长度值是一个DWORD类型值,给出字符串的字节长度,但不包括结尾零。在上例,"Bob"含有3个Unicode字符(不计结尾零),6个字节长。因为明确给出了字符串长度,所以当BSTR数据在不同的处理器和计算机之间传送时,COM库能够知道应该传送的数据量。 附带说一下,BSTR可以包含任何数据块,不单是字符。它甚至可以包容内嵌零字符数据。这些不在本文讨论范围。 C++中的BSTR变量其实就是指向字符串首字符的指针。BSTR是这样定义的: typedef OLECHAR* BSTR; 要知道为什么向一个需要BSTR类型数据的函数传递LPCWSTR类型数据是不安全的,就别忘了BSTR必须在字符串开头的四个字节保留字符串长度值。但LPCWSTR字符串中没有这个值。当其它的处理过程(如Word)要寻找BSTR的长度值时就会找到一堆垃圾或堆栈中的其它数据或其它随机数据。这就导致方法失效,当长度值太大时将导致崩溃。 许多应用接口都使用BSTR,但都用到二个最重要的函数来构造和析构BSTR。就是SysAllocString()和SysFreeString()函数。SysAllocString()将Unicode字符串拷贝到BSTR,SysFreeString()释放BSTR。示例如下: BSTR bstr = NULL;bstr = SysAllocString ( L"Hi Bob!" );if ( NULL == bstr ) // 内存溢出 // 这里使用bstrSysFreeString ( bstr ); 自动接口中的另一个数据类型是VARIANT。它用于在无类型语言,诸如JScript,VBScript,以及Visual Basic,之间传递数据。VARIANT可以包容许多不用类型的数据,如long和IDispatch*。如果VARIANT包含一个字符串,这个字符串是BSTR类型。在下文的VARIANT包装类中我还会谈及更多的VARIANT。 _bstr_t
字符串包装类 我已经说明了字符串的各种类型,现在讨论包装类。对于每个包装类,我都会说明它的对象构造过程和如何转换成C类型字符串指针。应用接口的调用,或构造另一个不同类型的字符串类,大多都要用到C类型指针。本文不涉及类的其它操作,如排序和比较等。 再强调一下,在完全了解转换结果之前不要随意使用强制类型转换。 CRT类 _bstr_t 是BSTR的完全包装类。实际上,它隐含了BSTR。它提供多种构造函数,能够处理隐含的C类型字符串。但它本身却不提供BSTR的处理机制,所以不能作为COM方法的输出参数[out]。如果要用到BSTR* 类型数据,用ATL的CComBSTR类更为方便。 _bstr_t 数据可以传递给需要BSTR数据的函数,但必须满足以下三个条件: 首先,_bstr_t 具有能够转换为wchar_t*类型数据的函数。 其次,根据BSTR定义,使得wchar_t* 和BSTR对于编译器来说是相同的。 第三,_bstr_t内部保留的指向内存数据块的指针 wchar_t* 要遵循BSTR格式。 满足这些条件,即使没有相应的BSTR转换文档,_bstr_t 也能正常工作。示例如下: // 构造_bstr_t bs1 = "char string"; // 从LPCSTR构造 _bstr_t bs2 = L"wide char string"; // 从LPCWSTR构造_bstr_t bs3 = bs1; // 拷贝另一个 _bstr_t_variant_t v = "Bob";_bstr_t bs4 = v; // 从一个含有字符串的 _variant_t 构造// 数据萃取LPCSTR psz1 = bs1; // 自动转换到MBCS字符串LPCSTR psz2 = (LPCSTR) bs1; // cast OK, 同上LPCWSTR pwsz1 = bs1; // 返回内部的Unicode字符串LPCWSTR pwsz2 = (LPCWSTR) bs1; // cast OK, 同上BSTR bstr = bs1.copy(); // 拷贝bs1, 返回BSTR// ... SysFreeString ( bstr ); _variant_t _variant_t 是VARIANT的完全包装类。它提供多种构造函数和数据转换函数。本文仅讨论与字符串有关的操作。 // 构造_variant_t v1 = "char string"; // 从LPCSTR 构造_variant_t v2 = L"wide char string"; // 从LPCWSTR 构造_bstr_t bs1 = "Bob";_variant_t v3 = bs1; // 拷贝一个 _bstr_t 对象// 数据萃取_bstr_t bs2 = v1; // 从VARIANT中提取BSTR_bstr_t bs3 = (_bstr_t) v1; // cast OK, 同上 另外要注意 _variant_t 不能直接转换成MBCS字符串。要建立一个过渡的_bstr_t 变量,用其它提供转换Unicode到MBCS的类函数,或ATL转换宏来转换。 与_bstr_t 不同,_variant_t 数据可以作为参数直接传送给COM方法。_variant_t 继承了VARIANT类型,所以在需要使用VARIANT的地方使用_variant_t 是C++语言规则允许的。 STL类
STL类 basic_string预定义了二个特例:string,含有char类型字符;which,含有wchar_t类型字符。没有内建的TCHAR特例,可用下面的代码实现: // 特例化typedef basic_string tstring; // TCHAR字符串// 构造string str = "char string"; // 从LPCSTR构造wstring wstr = L"wide char string"; // 从LPCWSTR构造tstring tstr = _T("TCHAR string"); // 从LPCTSTR构造// 数据萃取LPCSTR psz = str.c_str(); // 指向str缓冲区的只读指针LPCWSTR pwsz = wstr.c_str(); // 指向wstr缓冲区的只读指针LPCTSTR ptsz = tstr.c_str(); // 指向tstr缓冲区的只读指针 与_bstr_t 不同,basic_string不能在字符集之间进行转换。但是如果一个构造函数接受相应的字符类型,可以将由c_str()返回的指针传递给这个构造函数。例如: // 从basic_string构造_bstr_t _bstr_t bs1 = str.c_str(); // 从LPCSTR构造 _bstr_t_bstr_t bs2 = wstr.c_str(); // 从LPCWSTR构造 _bstr_t // 简单接口struct IStuff : public IUnknown{ // 略去COM程序... STDMETHOD(SetText)(BSTR bsText); STDMETHOD(GetText)(BSTR* pbsText);}; CComBSTR bs1;CComBSTR bs2 = "new text";pStuff->GetText ( &bs1 ); // ok, 取得内部BSTR地址 pStuff->SetText ( bs2 ); // ok, 调用BSTR转换 pStuff->SetText ( (BSTR) bs2 ); // cast ok, 同上 // 构造CComBSTR bs1 = "char string"; // 从LPCSTR构造CComBSTR bs2 = L"wide char string"; // 从LPCWSTR构造CComBSTR bs3 = bs1; // 拷贝CComBSTRCComBSTR bs4;bs4.LoadString ( IDS_SOME_STR ); // 从字符串表加载// 数据萃取BSTR bstr1 = bs1; // 返回内部BSTR,但不可修改!BSTR bstr2 = (BSTR) bs1; // cast ok, 同上BSTR bstr3 = bs1.Copy(); // 拷贝bs1, 返回BSTRBSTR bstr4;bstr4 = bs1.Detach(); // bs1不再管理它的BSTR// ...SysFreeString ( bstr3 );SysFreeString ( bstr4 ); 最后讨论一下引用操作符(operator &)。它的超越使得有些STL集合(如list)不能直接使用CComBSTR。在集合上使用引用操作返回指向包容类的指针。但是在CComBSTR上使用引用操作,返回的是BSTR*,不是CComBSTR*。不过可以用ATL的CAdapt类来解决这个问题。例如,要建立一个CComBSTR的队列,可以声明为: std::list< CAdapt> bstr_list; CComVariant // 构造CComVariant v1 = "char string"; // 从LPCSTR构造CComVariant v2 = L"wide char string"; // 从LPCWSTR构造CComBSTR bs1 = "BSTR bob";CComVariant v3 = (BSTR) bs1; // 从BSTR拷贝// 数据萃取CComBSTR bs2 = v1.bstrVal; // 从VARIANT提取BSTR CComVariant v4 = ... // 从某种类型初始化 v4CComBSTR bs3;if ( SUCCEEDED( v4.ChangeType ( VT_BSTR ) )) bs3 = v4.bstrVal; ATL转换宏 ATL转换宏
要使用宏转换,程序中要包含atlconv.h头文件。可以在非ATL程序中使用宏转换,因为头文件不依赖其它的ATL,也不需要 _Module全局变量。如在函数中使用转换宏,在函数起始处先写上USES_CONVERSION宏。它表明某些局部变量由宏控制使用。 转换得到的结果字符串,只要不是BSTR,都存储在堆栈中。如果要在函数外使用这些字符串,就要将这些字符串拷贝到其它的字符串类。如果结果是BSTR,内存不会自动释放,因此必须将返回值分配给一个BSTR变量或BSTR的包装类,以避免内存泄露。 下面是若干宏转换示例: // 带有字符串的函数:void Foo ( LPCWSTR wstr );void Bar ( BSTR bstr );// 返回字符串的函数:void Baz ( BSTR* pbstr );#include main(){using std::string;USES_CONVERSION; // 声明局部变量由宏控制使用// 示例1:送一个MBCS字符串到Foo()LPCSTR psz1 = "Bob";string str1 = "Bob";Foo ( A2CW(psz1) ); Foo ( A2CW(str1.c_str()) );// 示例2:将MBCS字符串和Unicode字符串送到Bar()LPCSTR psz2 = "Bob";LPCWSTR wsz = L"Bob";BSTR bs1;CComBSTR bs2;bs1 = A2BSTR(psz2); // 创建 BSTR bs2.Attach ( W2BSTR(wsz) ); // 同上,分配到CComBSTRBar ( bs1 ); Bar ( bs2 );SysFreeString ( bs1 ); // 释放bs1 // 不必释放bs2,由CComBSTR释放。// 示例3:转换由Baz()返回的BSTRBSTR bs3 = NULL;string str2;Baz ( &bs3 ); // Baz() 填充bs3内容str2 = W2CA(bs3); // 转换为MBCS字符串 SysFreeString ( bs3 ); // 释放bs3} MFC类 MFC的CString含有TCHAR,它的实际字符类型取决于预处理标记的设置。通常,CString象STL字符串一样是不透明对象,只能用CString的方法来修改。CString比STL字符串更优越的是它的构造函数接受MBCS和Unicode字符串。并且可以转换为LPCTSTR,因此可以向接受LPCTSTR的函数直接传递CString对象,不必调用c_str()方法。 // 构造CString s1 = "char string"; // 从LPCSTR构造CString s2 = L"wide char string"; // 从LPCWSTR构造CString s3 ( ' ', 100 ); // 预分配100字节,填充空格CString s4 = "New window text";// 可以在LPCTSTR处使用CString:SetWindowText ( hwndSomeWindow, s4 );// 或者,显式地做强制类型转换:SetWindowText ( hwndSomeWindow, (LPCTSTR) s4 ); // 从字符串表构造/加载CString s5 ( (LPCTSTR) IDS_SOME_STR ); // 从字符串表加载CString s6, s7;// 从字符串表加载 s6.LoadString ( IDS_SOME_STR );// 从字符串表加载打印格式的字符串 s7.Format ( IDS_SOME_FORMAT, "bob", nSomeStuff, ... ); 注意,CString只允许一种强制类型转换,即强制转换为LPCTSTR。强制转换为LPTSTR (非常量指针)是错误的。按照老习惯,将CString强制转换为LPTSTR只能伤害自己。有时在程序中没有发现出错,那只是碰巧。转换到非常量指针的正确方法是调用GetBuffer()方法。 下面以往队列加入元素为例说明如何正确地使用CString: CString str = _T("new text");LVITEM item = {0};item.mask = LVIF_TEXT; item.iItem = 1; item.pszText = (LPTSTR)(LPCTSTR) str; // 错! item.pszText = str.GetBuffer(0); // 正确ListView_SetItem ( &item ); str.ReleaseBuffer(); // 将队列返回给str 上面示例的出错语句可以通过编译,甚至可以正常工作,如果恰好就是这个类型。但这不证明语法正确。进行非常量的强制类型转换,打破了面向对象的封装原则,并逾越了CString的内部操作。如果你习惯进行这样的强制类型转换,终会遇到出错,可你未必知道错在何处,因为你到处都在做这样的转换,而代码也都能运行。
知道为什么人们总在抱怨有缺陷的软件吗?不正确的代码就臭虫的滋生地。然道你愿意编写明知有错的代码让臭虫有机可乘?还是花些时间学习CString的正确用法让你的代码能够100%的正确吧。 CString还有二个函数能够从CString中得到BSTR,并在必要时转换成Unicode。那就是AllocSysString()和SetSysString()。除了SetSysString()使用BSTR*参数外,二者一样。 // 转换成BSTRCString s5 = "Bob!";BSTR bs1 = NULL, bs2 = NULL;bs1 = s5.AllocSysString(); s5.SetSysString ( &bs2 );// ... SysFreeString ( bs1 ); SysFreeString ( bs2 ); // 构造CString s1 = _T("tchar string");COleVariant v1 = _T("Bob"); // 从LPCTSTR构造COleVariant v2 = s1; // 从CString拷贝 // 数据萃取COleVariant v3 = ...; // 从某种类型构造v3BSTR bs = NULL;try { v3.ChangeType ( VT_BSTR ); bs = v3.bstrVal; } catch ( COleException* e ) { // 出错,无法转换 }SysFreeString ( bs );
WTL类 WTL的CString与MFC的CString的行为完全相同,参阅上面关于MFC CString的说明即可。 CLR 及 VC 7 类 // 构造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 ); // 输出trueConsole::WriteLine ( ms1 == ms3); // 输出false Console::WriteLine ( ms1->CompareTo(ms2) ); Console::WriteLine ( ms1->CompareTo(ms3) ); 在String和MFC 7的CString之间转换很容易。CString可以转换为LPCTSTR,String有接受char* 和 wchar_t* 的二种构造函数。因此可以直接把CString传递给String的构造函数: CString s1 ( "hello world" ); String* s2 ( s1 ); // 从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 ); 字符串类的打印格式函数 例如,要向ATLTRACE()传递一个_bstr_t 里的字符串,必须显式用(LPCSTR)或 (LPCWSTR)进行强制类型转换:
_bstr_t bs = L"Bob!"; ATLTRACE("The string is: %s in line %d\n", (LPCSTR) bs, nLine); 所有类的总结 Class 附注: 虽然 _bstr_t 可以转换为非常量指针,但对内部缓冲区的修改可能导致内存溢出,或在释放BSTR时导致内存泄露。 |