青岛美容化妆品外贸网站建设,私活做网站,网站开发上传视频教程,常德网站建设求职简历CString 操作指南原著#xff1a;Joseph M. Newcomer翻译#xff1a;littleloach原文出处#xff1a;codeproject#xff1a;CString Management通过阅读本文你可以学习如何有效地使用 CString。 CString 是一种很有用的数据类型。它们很大程度上简化了MFC中的许多操作Joseph M. Newcomer翻译littleloach原文出处codeprojectCString Management通过阅读本文你可以学习如何有效地使用 CString。 CString 是一种很有用的数据类型。它们很大程度上简化了MFC中的许多操作使得MFC在做字符串操作的时候方便了很多。不管怎样使用CString有很多特殊的技巧特别是对于纯C背景下走出来的程序员来说有点难以学习。这篇文章就来讨论这些技巧。 使用CString可以让你对字符串的操作更加直截了当。这篇文章不是CString的完全手册但囊括了大部分常见基本问题。这篇文章包括以下内容 CString 对象的连接 格式化字符串包括 int 型转化为 CString CString 型转化成 int 型 CString 型和 char* 类型的相互转化 char* 转化成 CString CString 转化成 char* 之一使用LPCTSTR强制转化 CString 转化成 char* 之二使用CString对象的GetBuffer方法 CString 转化成 char* 之三: 和控件的接口 CString 型转化成 BSTR 型 BSTR 型转化成 CString 型 VARIANT 型转化成 CString 型 载入字符串表资源 CString 和临时对象 CString 的效率 总结 下面我分别讨论。 1、CString 对象的连接 能体现出 CString 类型方便性特点的一个方面就字符串的连接使用 CString 类型你能很方便地连接两个字符串正如下面的例子CString gray(Gray);
CString cat(Cat);
CString graycat gray cat;要比用下面的方法好得多char gray[] Gray;
char cat[] Cat;
char * graycat malloc(strlen(gray) strlen(cat) 1);
strcpy(graycat, gray);
strcat(graycat, cat); 2、格式化字符串 与其用 sprintf() 函数或 wsprintf() 函数来格式化一个字符串还不如用 CString 对象的Format()方法CString s;
s.Format(_T(The total is %d), total); 用这种方法的好处是你不用担心用来存放格式化后数据的缓冲区是否足够大这些工作由CString类替你完成。 格式化是一种把其它不是字符串类型的数据转化为CString类型的最常用技巧比如把一个整数转化成CString类型可用如下方法CString s;
s.Format(_T(%d), total); 我总是对我的字符串使用_T()宏这是为了让我的代码至少有Unicode的意识当然关于Unicode的话题不在这篇文章的讨论范围。_T()宏在8位字符环境下是如下定义的#define _T(x) x // 非Unicode版本non-Unicode version而在Unicode环境下是如下定义的#define _T(x) L##x // Unicode版本Unicode version所以在Unicode环境下它的效果就相当于s.Format(L%d, total); 如果你认为你的程序可能在Unicode的环境下运行那么开始在意用 Unicode 编码。比如说不要用 sizeof() 操作符来获得字符串的长度因为在Unicode环境下就会有2倍的误差。我们可以用一些方法来隐藏Unicode的一些细节比如在我需要获得字符长度的时候我会用一个叫做DIM的宏这个宏是在我的dim.h文件中定义的我会在我写的所有程序中都包含这个文件#define DIM(x) ( sizeof((x)) / sizeof((x)[0]) ) 这个宏不仅可以用来解决Unicode的字符串长度的问题也可以用在编译时定义的表格上它可以获得表格的项数如下class Whatever { ... };
Whatever data[] {
{ ... },
...
{ ... },
};
for(int i 0; i DIM(data); i) // 扫描表格寻找匹配项。
这里要提醒你的就是一定要注意那些在参数中需要真实字节数的API函数调用如果你传递字符个数给它它将不能正常工作。如下TCHAR data[20];
lstrcpyn(data, longstring, sizeof(data) - 1); // WRONG!
lstrcpyn(data, longstring, DIM(data) - 1); // RIGHT
WriteFile(f, data, DIM(data), bytesWritten, NULL); // WRONG!
WriteFile(f, data, sizeof(data), bytesWritten, NULL); // RIGHT造成以上原因是因为lstrcpyn需要一个字符个数作为参数但是WriteFile却需要字节数作为参数。同样需要注意的是有时候需要写出数据的所有内容。如果你仅仅只想写出数据的真实长度你可能会认为你应该这样做WriteFile(f, data, lstrlen(data), bytesWritten, NULL); // WRONG但是在Unicode环境下它不会正常工作。正确的做法应该是这样WriteFile(f, data, lstrlen(data) * sizeof(TCHAR), bytesWritten, NULL); // RIGHT 因为WriteFile需要的是一个以字节为单位的长度。可能有些人会想“在非Unicode的环境下运行这行代码就意味着总是在做一个多余的乘1操作这样不会降低程序的效率吗”这种想法是多余的你必须要了解编译器实际上做了什么没有哪一个C或C编译器会把这种无聊的乘1操作留在代码中。在Unicode环境下运行的时候你也不必担心那个乘2操作会降低程序的效率记住这只是一个左移一位的操作而已编译器也很乐意为你做这种替换。 使用_T宏并不是意味着你已经创建了一个Unicode的程序你只是创建了一个有Unicode意识的程序而已。如果你在默认的8-bit模式下编译你的程序的话得到的将是一个普通的8-bit的应用程序这里的8-bit指的只是8位的字符编码并不是指8位的计算机系统当你在Unicode环境下编译你的程序时你才会得到一个Unicode的程序。记住CString 在 Unicode 环境下里面包含的可都是16位的字符哦。 3、CString 型转化成 int 型 把 CString 类型的数据转化成整数类型最简单的方法就是使用标准的字符串到整数转换例程。 虽然通常你怀疑使用_atoi()函数是一个好的选择它也很少会是一个正确的选择。如果你准备使用 Unicode 字符你应该用_ttoi()它在 ANSI 编码系统中被编译成_atoi()而在 Unicode 编码系统中编译成_wtoi()。你也可以考虑使用_tcstoul()或者_tcstol()它们都能把字符串转化成任意进制的长整数如二进制、八进制、十进制或十六进制不同点在于前者转化后的数据是无符号的unsigned而后者相反。看下面的例子CString hex _T(FAB);
CString decimal _T(4011);
ASSERT(_tcstoul(hex, 0, 16) _ttoi(decimal)); 4、CString 型和 char* 类型的相互转化 这是初学者使用 CString 时最常见的问题。有了 C 的帮助很多问题你不需要深入的去考虑它直接拿来用就行了但是如果你不能深入了解它的运行机制又会有很多问题让你迷惑特别是有些看起来没有问题的代码却偏偏不能正常工作。比如你会奇怪为什么不能写向下面这样的代码呢CString graycat Gray Cat;或者这样CString graycat(Gray Cat); 事实上编译器将抱怨上面的这些尝试。为什么呢因为针对CString 和 LPCTSTR数据类型的各种各样的组合“ ” 运算符 被定义成一个重载操作符。而不是两个 LPCTSTR 数据类型它是底层数据类型。你不能对基本数据如 int、char 或者 char*类型重载 C 的运算符。你可以象下面这样做CString graycat CString(Gray) CString(Cat);或者这样CString graycat CString(Gray) Cat;研究一番就会发现“ ”总是使用在至少有一个 CString 对象和一个 LPCSTR 的场合。注意编写有 Unicode 意识的代码总是一件好事比如CString graycat CString(_T(Gray)) _T(Cat);这将使得你的代码可以直接移植。char* 转化为 CString 现在你有一个 char* 类型的数据或者说一个字符串。怎么样创建 CString 对象呢这里有一些例子char * p This is a test;或者象下面这样更具有 Unicode 意识TCHAR * p _T(This is a test)或LPTSTR p _T(This is a test);你可以使用下面任意一种写法CString s This is a test; // 8-bit only
CString s _T(This is a test); // Unicode-aware
CString s(This is a test); // 8-bit only
CString s(_T(This is a test)); // Unicode-aware
CString s p;
CString s(p); 用这些方法可以轻松将常量字符串或指针转换成 CString。需要注意的是字符的赋值总是被拷贝到 CString 对象中去的所以你可以象下面这样操作TCHAR * p _T(Gray);
CString s(p);
p _T(Cat);
s p;结果字符串肯定是“GrayCat”。CString 类还有几个其它的构造函数但是这里我们不考虑它如果你有兴趣可以自己查看相关文档。事实上CString 类的构造函数比我展示的要复杂比如CString s This is a test; 这是很草率的编码但是实际上它在 Unicode 环境下能编译通过。它在运行时调用构造函数的 MultiByteToWideChar 操作将 8 位字符串转换成 16 位字符串。不管怎样如果 char * 指针是网络上传输的 8 位数据这种转换是很有用的。CString 转化成 char* 之一强制类型转换为 LPCTSTR 这是一种略微硬性的转换有关“正确”的做法人们在认识上还存在许多混乱正确的使用方法有很多但错误的使用方法可能与正确的使用方法一样多。 我们首先要了解 CString 是一种很特殊的 C 对象它里面包含了三个值一个指向某个数据缓冲区的指针、一个是该缓冲中有效的字符记数以及一个缓冲区长度。 有效字符数的大小可以是从0到该缓冲最大长度值减1之间的任何数因为字符串结尾有一个NULL字符。字符记数和缓冲区长度被巧妙隐藏。 除非你做一些特殊的操作否则你不可能知道给CString对象分配的缓冲区的长度。这样即使你获得了该0缓冲的地址你也无法更改其中的内容不能截短字符串也 绝对没有办法加长它的内容否则第一时间就会看到溢出。 LPCTSTR 操作符或者更明确地说就是 TCHAR * 操作符在 CString 类中被重载了该操作符的定义是返回缓冲区的地址因此如果你需要一个指向 CString 的 字符串指针的话可以这样做CString s(GrayCat);
LPCTSTR p s; 它可以正确地运行。这是由C语言的强制类型转化规则实现的。当需要强制类型转化时C规测容许这种选择。比如你可以将浮点数定义为将某个复数 有一对浮点数进行强制类型转换后只返回该复数的第一个浮点数也就是其实部。可以象下面这样Complex c(1.2f, 4.8f);
float realpart c;如果(float)操作符定义正确的话那么实部的的值应该是1.2。 这种强制转化适合所有这种情况例如任何带有 LPCTSTR 类型参数的函数都会强制执行这种转换。 于是你可能有这样一个函数也许在某个你买来的DLL中BOOL DoSomethingCool(LPCTSTR s);你象下面这样调用它CString file(c://myfiles//coolstuff)
BOOL result DoSomethingCool(file); 它能正确运行。因为 DoSomethingCool 函数已经说明了需要一个 LPCTSTR 类型的参数因此 LPCTSTR 被应用于该参数在 MFC 中就是返回的串地址。如果你要格式化字符串怎么办呢CString graycat(GrayCat);
CString s;
s.Format(Mew! I love %s, graycat); 注意由于在可变参数列表中的值在函数说明中是以“...”表示的并没有隐含一个强制类型转换操作符。你会得到什么结果呢 一个令人惊讶的结果我们得到的实际结果串是Mew! I love GrayCat。 因为 MFC 的设计者们在设计 CString 数据类型时非常小心 CString 类型表达式求值后指向了字符串所以这里看不到任何象 Format 或 sprintf 中的强制类型转换你仍然可以得到正确的行为。描述 CString 的附加数据实际上在 CString 名义地址之后。 有一件事情你是不能做的那就是修改字符串。比如你可能会尝试用“,”代替“.”不要做这样的如果你在乎国际化问题你应该使用十进制转换的 National Language Support 特性下面是个简单的例子CString v(1.00); // 货币金额两位小数
LPCTSTR p v;
p[lstrlen(p) - 3] ,; 这时编译器会报错因为你赋值了一个常量串。如果你做如下尝试编译器也会错strcat(p, each); 因为 strcat 的第一个参数应该是 LPTSTR 类型的数据而你却给了一个 LPCTSTR。 不要试图钻这个错误消息的牛角尖这只会使你自己陷入麻烦 原因是缓冲有一个计数它是不可存取的它位于 CString 地址之下的一个隐藏区域如果你改变这个串缓冲中的字符计数不会反映所做的修改。此外如果字符串长度恰好是该字符串物理限制的长度梢后还会讲到这个问题那么扩展该字符串将改写缓冲以外的任何数据那是你无权进行写操作的内存不对吗你会毁换坏不属于你的内存。这是应用程序真正的死亡处方。CString转化成char* 之二使用 CString 对象的 GetBuffer 方法 如果你需要修改 CString 中的内容它有一个特殊的方法可以使用那就是 GetBuffer它的作用是返回一个可写的缓冲指针。 如果你只是打算修改字符或者截短字符串你完全可以这样做CString s(_T(File.ext));
LPTSTR p s.GetBuffer();
LPTSTR dot strchr(p, .); // OK, should have used s.Find...
if(p ! NULL)
*p _T(/0);
s.ReleaseBuffer(); 这是 GetBuffer 的第一种用法也是最简单的一种不用给它传递参数它使用默认值 0意思是“给我这个字符串的指针我保证不加长它”。当你调用 ReleaseBuffer 时字符串的实际长度会被重新计算然后存入 CString 对象中。 必须强调一点在 GetBuffer 和 ReleaseBuffer 之间这个范围一定不能使用你要操作的这个缓冲的 CString 对象的任何方法。因为 ReleaseBuffer 被调用之前该 CString 对象的完整性得不到保障。研究以下代码CString s(...);
LPTSTR p s.GetBuffer();
//... 这个指针 p 发生了很多事情
int n s.GetLength(); // 很糟D!!!!! 有可能给出错误的答案!!!
s.TrimRight(); // 很糟!!!!! 不能保证能正常工作!!!!
s.ReleaseBuffer(); // 现在应该 OK
int m s.GetLength(); // 这个结果可以保证是正确的。
s.TrimRight(); // 将正常工作。 假设你想增加字符串的长度你首先要知道这个字符串可能会有多长好比是声明字符串数组的时候用char buffer[1024];表示 1024 个字符空间足以让你做任何想做得事情。在 CString 中与之意义相等的表示法LPTSTR p s.GetBuffer(1024); 调用这个函数后你不仅获得了字符串缓冲区的指针而且同时还获得了长度至少为 1024 个字符的空间注意我说的是“字符”而不是“字节”因为 CString 是以隐含方式感知 Unicode 的。 同时还应该注意的是如果你有一个常量串指针这个串本身的值被存储在只读内存中如果试图存储它即使你已经调用了 GetBuffer 并获得一个只读内存的指针存入操作会失败并报告存取错误。我没有在 CString 上证明这一点但我看到过大把的 C 程序员经常犯这个错误。 C 程序员有一个通病是分配一个固定长度的缓冲对它进行 sprintf 操作然后将它赋值给一个 CStringchar buffer[256];
sprintf(buffer, %......, args, ...); // ... 部分省略许多细节
CString s buffer;虽然更好的形式可以这么做CString s;
s.Format(_T(%....), args, ...);如果你的字符串长度万一超过 256 个字符的时候不会破坏堆栈。 另外一个常见的错误是既然固定大小的内存不工作那么就采用动态分配字节这种做法弊端更大int len lstrlen(parm1) 13 lstrlen(parm2) 10 100;
char * buffer new char[len];
sprintf(buffer, %s is equal to %s, valid data, parm1, parm2);
CString s buffer;
......
delete [] buffer;它可以能被简单地写成CString s;
s.Format(_T(%s is equal to %s, valid data), parm1, parm2); 需要注意 sprintf 例子都不是 Unicode 就绪的尽管你可以使用 tsprintf 以及用 _T() 来包围格式化字符串但是基本 思路仍然是在走弯路这这样很容易出错。CString to char * 之三和控件的接口 我们经常需要把一个 CString 的值传递给一个控件比如CTreeCtrl。MFC为我们提供了很多便利来重载这个操作但是 在大多数情况下你使用“原始”形式的更新因此需要将墨某个串指针存储到 TVINSERTITEMSTRUCT 结构的 TVITEM 成员中。如下TVINSERTITEMSTRUCT tvi;
CString s;
// ... 为s赋一些值。
tvi.item.pszText s; // Compiler yells at you here
// ... 填写tvi的其他域
HTREEITEM ti c_MyTree.InsertItem(tvi); 为什么编译器会报错呢明明看起来很完美的用法啊但是事实上如果你看看 TVITEM 结构的定义你就会明白在 TVITEM 结构中 pszText 成员的声明如下LPTSTR pszText;
int cchTextMax; 因此赋值不是赋给一个 LPCTSTR 类型的变量而且编译器无法知道如何将赋值语句右边强制转换成 LPCTSTR。好吧你说那我就改成这样tvi.item.pszText (LPCTSTR)s; //编译器依然会报错。 编译器之所以依然报错是因为你试图把一个 LPCTSTR 类型的变量赋值给一个 LPTSTR 类型的变量这种操作在C或C中是被禁止的。你不能用这种方法 来滥用常量指针与非常量指针概念否则会扰乱编译器的优化机制使之不知如何优化你的程序。比如如果你这么做const int i ...;
//... do lots of stuff
... a[i]; // usage 1
// ... lots more stuff
... a[i]; // usage 2 那么编译器会以为既然 i 是 const 所以 usage1和usage2的值是相同的并且它甚至能事先计算好 usage1 处的 a[i] 的地址然后保留着在后面的 usage2 处使用而不是重新计算。如果你按如下方式写的话const int i ...;
int * p i;
//... do lots of stuff
... a[i]; // usage 1
// ... lots more stuff
(*p); // mess over compilers assumption
// ... and other stuff
... a[i]; // usage 2 编译器将认为 i 是常量从而 a[i] 的位置也是常量这样间接地破坏了先前的假设。因此你的程序将会在 debug 编译模式没有优化和 release 编译模式完全优化中反映出不同的行为这种情况可不好所以当你试图把指向 i 的指针赋值给一个 可修改的引用时会被编译器诊断为这是一种伪造。这就是为什么LPCTSTR强制类型转化不起作用的原因。 为什么不把该成员声明成 LPCTSTR 类型呢因为这个结构被用于读写控件。当你向控件写数据时文本指针实际上被当成 LPCTSTR而当你从控件读数据 时你必须有一个可写的字符串。这个结构无法区分它是用来读还是用来写。因此你会常常在我的代码中看到如下的用法tvi.item.pszText (LPTSTR)(LPCTSTR)s; 它把 CString 强制类型转化成 LPCTSTR也就是说先获得改字符串的地址然后再强制类型转化成 LPTSTR以便可以对之进行赋值操作。 注意这只有在使用 Set 或 Insert 之类的方法才有效如果你试图获取数据则不能这么做。 如果你打算获取存储在控件中的数据则方法稍有不同例如对某个 CTreeCtrl 使用 GetItem 方法我想获取项目的文本。我知道这些 文本的长度不会超过 MY_LIMIT因此我可以这样写TVITEM tvi;
// ... assorted initialization of other fields of tvi
tvi.pszText s.GetBuffer(MY_LIMIT);
tvi.cchTextMax MY_LIMIT;
c_MyTree.GetItem(tvi);
s.ReleaseBuffer(); 可以看出来其实上面的代码对所有类型的 Set 方法都适用但是并不需要这么做因为所有的类 Set 方法包括 Insert方法不会改变字符串的内容。但是当你需要写 CString 对象时必须保证缓冲是可写的这正是 GetBuffer 所做的事情。再次强调 一旦做了一次 GetBuffer 调用那么在调用 ReleaseBuffer 之前不要对这个 CString 对象做任何操作。 5、CString 型转化成 BSTR 型 当我们使用 ActiveX 控件编程时经常需要用到将某个值表示成 BSTR 类型。BSTR 是一种记数字符串Intel平台上的宽字符串Unicode并且 可以包含嵌入的 NULL 字符。你可以调用 CString 对象的 AllocSysString 方法将 CString 转化成 BSTRCString s;
s ... ; // whatever
BSTR b s.AllocSysString(); 现在指针 b 指向的就是一个新分配的 BSTR 对象该对象是 CString 的一个拷贝包含终结 NULL字符。现在你可以将它传递给任何需要 BSTR 的接口。通常BSTR 由接收它的组件来释放如果你需要自己释放 BSTR 的话可以这么做::SysFreeString(b); 对于如何表示传递给 ActiveX 控件的字符串在微软内部曾一度争论不休最后 Visual Basic 的人占了上风BSTR“Basic String”的首字母缩写就是这场争论的结果。 6、BSTR 型转化成 CString 型 由于 BSTR 是记数 Unicode 字符串你可以用标准转换方法来创建 8 位的 CString。实际上这是 CString 内建的功能。在 CString 中 有特殊的构造函数可以把 ANSI 转化成 Unicode也可以把Unicode 转化成 ANSI。你同样可以从 VARIANT 类型的变量中获得 BSTR 类型的字符串VARIANT 类型是 由各种 COM 和 Automation (自动化)调用返回的类型。例如在一个ANSI程序中BSTR b;
b ...; // whatever
CString s(b NULL ? L : b) 对于单个的 BSTR 串来说这种用法可以工作得很好这是因为 CString 有一个特殊的构造函数以LPCWSTRBSTR正是这种类型 为参数并将它转化成 ANSI 类型。专门检查是必须的因为 BSTR 可能为空值而 CString 的构造函数对于 NULL 值情况考虑的不是很周到感谢 Brian Ross 指出这一点!。这种用法也只能处理包含 NUL 终结字符的单字符串如果要转化含有多个 NULL 字符 串你得额外做一些工作才行。在 CString 中内嵌的 NULL 字符通常表现不尽如人意应该尽量避免。 根据 C/C 规则如果你有一个 LPWSTR那么它别无选择只能和 LPCWSTR 参数匹配。在 Unicode 模式下它的构造函数是CString::CString(LPCTSTR);正如上面所表示的在 ANSI 模式下它有一个特殊的构造函数CString::CString(LPCWSTR); 它会调用一个内部的函数将 Unicode 字符串转换成 ANSI 字符串。在Unicode模式下有一个专门的构造函数该函数有一个参数是LPCSTR类型——一个8位 ANSI 字符串 指针该函数将它加宽为 Unicode 的字符串再次强调一定要检查 BSTR 的值是否为 NULL。 另外还有一个问题正如上文提到的BSTRs可以含有多个内嵌的NULL字符但是 CString 的构造函数只能处理某个串中单个 NULL 字符。 也就是说如果串中含有嵌入的 NUL字节CString 将会计算出错误的串长度。你必须自己处理它。如果你看看 strcore.cpp 中的构造函数你会发现 它们都调用了lstrlen也就是计算字符串的长度。 注意从 Unicode 到 ANSI 的转换使用带专门参数的 ::WideCharToMultiByte如果你不想使用这种默认的转换方式则必须编写自己的转化代码。 如果你在 UNICODE 模式下编译代码你可以简单地写成CString convert(BSTR b)
{
if(b NULL)
return CString(_T());
CString s(b); // in UNICODE mode
return s;
}
如果是 ANSI 模式则需要更复杂的过程来转换。注意这个代码使用与 ::WideCharToMultiByte 相同的参数值。所以你 只能在想要改变这些参数进行转换时使用该技术。例如指定不同的默认字符不同的标志集等。 CString convert(BSTR b)
{
CString s;
if(b NULL)
return s; // empty for NULL BSTR
#ifdef UNICODE
s b;
#else
LPSTR p s.GetBuffer(SysStringLen(b) 1);
::WideCharToMultiByte(CP_ACP, // ANSI Code Page
0, // no flags
b, // source widechar string
-1, // assume NUL-terminated
p, // target buffer
SysStringLen(b)1, // target buffer length
NULL, // use system default char
NULL); // dont care if default used
s.ReleaseBuffer();
#endif
return s;
}
我并不担心如果 BSTR 包含没有映射到 8 位字符集的 Unicode 字符时会发生什么因为我指定了::WideCharToMultiByte 的最后两个参数为 NULL。这就是你可能需要改变的地方。 7、VARIANT 型转化成 CString 型 事实上我从来没有这么做过因为我没有用 COM/OLE/ActiveX 编写过程序。但是我在microsoft.public.vc.mfc 新闻组上看到了 Robert Quirk 的一篇帖子谈到了这种转化我觉得把他的文章包含在我的文章里是不太好的做法所以在这里多做一些解释和演示。如果和他的文章有相孛的地方可能是我的疏忽。 VARIANT 类型经常用来给 COM 对象传递参数或者接收从 COM 对象返回的值。你也能自己编写返回 VARIANT 类型的方法函数返回什么类型 依赖可能并且常常方法的输入参数比如在自动化操作中依赖与你调用哪个方法。IDispatch::Invoke 可能返回通过其一个参数一个 包含有BYTE、WORD、float、double、date、BSTR 等等 VARIANT 类型的结果详见 MSDN 上的 VARIANT 结构的定义。在下面的例子中假设 类型是一个BSTR的变体也就是说在串中的值是通过 bsrtVal 来引用其优点是在 ANSI 应用中有一个构造函数会把 LPCWCHAR 引用的值转换为一个 CString见 BSTR-to-CString 部分。在 Unicode 模式中将成为标准的 CString 构造函数参见对缺省::WideCharToMultiByte 转换的告诫以及你觉得是否可以接受大多数情况下你会满意的。VARIANT vaData;
vaData m_com.YourMethodHere();
ASSERT(vaData.vt VT_BSTR);
CString strData(vaData.bstrVal);你还可以根据 vt 域的不同来建立更通用的转换例程。为此你可能会考虑CString VariantToString(VARIANT * va)
{
CString s;
switch(va-vt)
{ /* vt */
case VT_BSTR:
return CString(vaData-bstrVal);
case VT_BSTR | VT_BYREF:
return CString(*vaData-pbstrVal);
case VT_I4:
s.Format(_T(%d), va-lVal);
return s;
case VT_I4 | VT_BYREF:
s.Format(_T(%d), *va-plVal);
case VT_R8:
s.Format(_T(%f), va-dblVal);
return s;
... 剩下的类型转换由读者自己完成
default:
ASSERT(FALSE); // unknown VARIANT type (this ASSERT is optional)
return CString();
} /* vt */
}
8、载入字符串表资源 如果你想创建一个容易进行语言版本移植的应用程序你就不能在你的源代码中直接包含本土语言字符串 下面这些例子我用的语言都是英语因为我的本土语是英语比如下面这种写法就很糟CString s There is an error; 你应该把你所有特定语言的字符串单独摆放调试信息、在发布版本中不出现的信息除外。这意味着向下面这样写比较好s.Format(_T(%d - %s), code, text); 在你的程序中文字字符串不是语言敏感的。不管怎样你必须很小心不要使用下面这样的串// fmt is Error in %s file %s
// readorwrite is reading or writing
s.Format(fmt, readorwrite, filename); 这是我的切身体会。在我的第一个国际化的应用程序中我犯了这个错误尽管我懂德语知道在德语的语法中动词放在句子的最后面我们的德国方面的发行人还是苦苦的抱怨他们不得不提取那些不可思议的德语错误提示信息然后重新格式化以让它们能正常工作。比较好的办法也是我现在使用的办法是使用两个字符串一个用 于读一个用于写在使用时加载合适的版本使得它们对字符串参数是非敏感的。也就是说加载整个格式而不是加载串“reading”“writing”// fmt is Error in reading file %s
// Error in writing file %s
s.Format(fmt, filename); 一定要注意如果你有好几个地方需要替换你一定要保证替换后句子的结构不会出现问题比如在英语中可以是主语-宾语主语-谓语动词-宾语的结构等等。 在这里我们并不讨论 FormatMessage其实它比 sprintf/Format 还要有优势但是不太容易和CString 结合使用。解决这种问题的办法就是我们按照参数出现在参数表中的位置给参数取名字这样在你输出的时候就不会把他们的位置排错了。 接下来我们讨论我们这些独立的字符串放在什么地方。我们可以把字符串的值放入资源文件中的一个称为 STRINGTABLE 的段中。过程如下首先使用 Visual Studio 的资源编辑器创建一个字符串然后给每一个字符串取一个ID一般我们给它取名字都以 IDS_开头。所以如果你有一个信息你可以创建一个字符串资源然后取名为 IDS_READING_FILE另外一个就取名为 IDS_WRITING_FILE。它们以下面的形式出现在你的 .rc 文件中STRINGTABLE
IDS_READING_FILE Reading file %s
IDS_WRITING_FILE Writing file %s
END注意这些资源都以 Unicode 的格式保存不管你是在什么环境下编译。他们在Win9x系统上也是以Unicode 的形式存在虽然 Win9x 不能真正处理 Unicode。然后你可以这样使用这些资源// 在使用资源串表之前程序是这样写的 CString fmt;
if(...)
fmt Reading file %s;
else
fmt Writing file %s;
...
// much later
CString s;
s.Format(fmt, filename);
// 使用资源串表之后程序这样写 CString fmt;
if(...)
fmt.LoadString(IDS_READING_FILE);
else
fmt.LoadString(DS_WRITING_FILE);
...
// much later
CString s;
s.Format(fmt, filename);
现在你的代码可以移植到任何语言中去。LoadString 方法需要一个字符串资源的 ID 作为参数然后它从 STRINGTABLE 中取出它对应的字符串赋值给 CString 对象。 CString 对象的构造函数还有一个更加聪明的特征可以简化 STRINGTABLE 的使用。这个用法在 CString::CString 的文档中没有指出但是在 构造函数的示例程序中使用了。为什么这个特性没有成为正式文档的一部分而是放在了一个例子中我记不得了——【译者注从这句话看作者可能是CString的设计者。其实前面还有一句类似的话。说他没有对使用GetBuffer(0)获得的指针指向的地址是否可读做有效性检查 】。这个特征就是如果你将一个字符串资源的ID强制类型转换为 LPCTSTR将会隐含调用 LoadString。因此下面两个构造字符串的例子具有相同的效果而且其 ASSERT 在debug模式下不会被触发CString s;
s.LoadString(IDS_WHATEVER);
CString t( (LPCTSTR)IDS_WHATEVER );
ASSERT(s t);//不会被触发说明s和t是相同的。 现在你可能会想这怎么可能工作呢我们怎么能把 STRINGTABLE ID 转化成一个指针呢很简单所有的字符串 ID 都在1~65535这个范围内也就是说它所有的高位都是0而我们在程序中所使用的指针是不可能小于65535的因为程序的低 64K 内存永远也不可能存在的如果你试图访问0x00000000到0x0000FFFF之间的内存将会引发一个内存越界错误。所以说1~65535的值不可能是一个内存地址所以我们可以用这些值来作为字符串资源的ID。 我倾向于使用 MAKEINTRESOURCE 宏显式地做这种转换。我认为这样可以让代码更加易于阅读。这是个只适合在 MFC 中使用的标准宏。你要记住大多数的方法即可以接受一个 UINT 型的参数也可以接受一个 LPCTSTR 型的参数这是依赖 C 的重载功能做到的。C重载函数带来的 弊端就是造成所有的强制类型转化都需要显示声明。同样你也可以给很多种结构只传递一个资源名。CString s;
s.LoadString(IDS_WHATEVER);
CString t( MAKEINTRESOURCE(IDS_WHATEVER));
ASSERT(s t); 告诉你吧我不仅只是在这里鼓吹事实上我也是这么做的。在我的代码中你几乎不可能找到一个字符串当然那些只是偶然在调试中出现的或者和语言无关的字符串除外。 9、CString 和临时对象 这是出现在 microsoft.public.vc.mfc 新闻组中的一个小问题我简单的提一下这个问题是有个程序员需要往注册表中写入一个字符串他写道 我试着用 RegSetValueEx() 设置一个注册表键的值但是它的结果总是令我困惑。当我用char[]声明一个变量时它能正常工作但是当我用 CString 的时候总是得到一些垃圾Yacute;Yacute;Yacute;Yacute;...Yacute;Yacute;Yacute;Yacute;Yacute;Yacute;为了确认是不是我的 CString 数据出了问题我试着用 GetBuffer然后强制转化成 char*LPCSTR。GetBuffer 返回的值是正确的但是当我把它赋值给 char* 时它就变成垃圾了。以下是我的程序段char* szName GetName().GetBuffer(20);
RegSetValueEx(hKey, Name, 0, REG_SZ,
(CONST BYTE *) szName,
strlen (szName 1));这个 Name 字符串的长度小于 20所以我不认为是 GetBuffer 的参数的问题。真让人困惑请帮帮我。亲爱的 Frustrated你犯了一个相当微妙的错误聪明反被聪明误正确的代码应该象下面这样CString Name GetName();
RegSetValueEx(hKey, _T(Name), 0, REG_SZ,
(CONST BYTE *) (LPCTSTR)Name,
(Name.GetLength() 1) * sizeof(TCHAR));
为什么我写的代码能行而你写的就有问题呢主要是因为当你调用 GetName 时返回的 CString 对象是一个临时对象。参见《C Reference manual》§12.2 在一些环境中编译器有必要创建一个临时对象这样引入临时对象是依赖于实现的。如果编译器引入的这个临时对象所属的类有构造函数的话编译器要确保这个类的构造函数被调用。同样的如果这个类声明有析构函数的话也要保证这个临时对象的析构函数被调用。 编译器必须保证这个临时对象被销毁了。被销毁的确切地点依赖于实现.....这个析构函数必须在退出创建该临时对象的范围之前被调用。 大部分的编译器是这样设计的在临时对象被创建的代码的下一个执行步骤处隐含调用这个临时对象的析构函数实现起来一般都是在下一个分号处。因此这个 CString 对象在 GetBuffer 调用之后就被析构了顺便提一句你没有理由给 GetBuffer 函数传递一个参数而且没有使用ReleaseBuffer 也是不对的。所以 GetBuffer 本来返回的是指向这个临时对象中字符串的地址的指针但是当这个临时对象被析构后这块内存就被释放了。然后 MFC 的调试内存分配器会重新为这块内存全部填上 0xDD显示出来刚好就是“Yacute;”符号。在这个时候你向注册表中写数据字符串的内容当然全被破坏了。 我们不应该立即把这个临时对象转化成 char* 类型应该先把它保存到一个 CString 对象中这意味着把临时对象复制了一份所以当临时的 CString 对象被析构了之后这个 CString 对象中的值依然保存着。这个时候再向注册表中写数据就没有问题了。 此外我的代码是具有 Unicode 意识的。那个操作注册表的函数需要一个字节大小使用lstrlen(Name1) 得到的实际结果对于 Unicode 字符来说比 ANSI 字符要小一半而且它也不能从这个字符串的第二个字符起开始计算也许你的本意是 lstrlen(Name) 1OK我承认我也犯了同样的错误。不论如何在 Unicode 模式下所有的字符都是2个字节大小我们需要处理这个问题。微软的文档令人惊讶地对此保持缄默REG_SZ 的值究竟是以字节计算还是以字符计算呢我们假设它指的是以字节为单位计算你需要对你的代码做一些修改来计算这个字符串所含有的字节大小。 10、CString 的效率 CString 的一个问题是它确实掩藏了一些低效率的东西。从另外一个方面讲它也确实可以被实现得更加高效你可能会说下面的代码CString s SomeCString1;
s SomeCString2;
s SomeCString3;
s ,;
s SomeCString4;比起下面的代码来效率要低多了char s[1024];
lstrcpy(s, SomeString1);
lstrcat(s, SomeString2);
lstrcat(s, SomeString 3);
lstrcat(s, ,);
lstrcat(s, SomeString4); 总之你可能会想首先它为 SomeCString1 分配一块内存然后把 SomeCString1 复制到里面然后发现它要做一个连接则重新分配一块新的足够大的内存大到能够放下当前的字符串加上SomeCString2把内容复制到这块内存 然后把 SomeCString2 连接到后面然后释放第一块内存并把指针重新指向新内存。然后为每个字符串重复这个过程。把这 4 个字符串连接起来效率多低啊。事实上在很多情况下根本就不需要复制源字符串在 操作符左边的字符串。 在 VC6.0 中Release 模式下所有的 CString 中的缓存都是按预定义量子分配的。所谓量子即确定为 64、128、256 或者 512 字节。这意味着除非字符串非常长连接字符串的操作实际上就是 strcat 经过优化后的版本因为它知道本地的字符串应该在什么地方结束所以不需要寻找字符串的结尾只需要把内存中的数据拷贝到指定的地方即可加上重新计算字符串的长度。所以它的执行效率和纯 C 的代码是一样的但是它更容易写、更容易维护和更容易理解。 如果你还是不能确定究竟发生了怎样的过程请看看 CString 的源代码strcore.cpp在你 vc98的安装目录的 mfc/src 子目录中。看看 ConcatInPlace 方法它被在所有的 操作符中调用。啊哈难道 CString 真的这么高效吗比如如果我创建CString cat(Mew!); 然后我并不是得到了一个高效的、精简的5个字节大小的缓冲区4个字符加一个结束字符系统将给我分配64个字节而其中59个字节都被浪费了。 如果你也是这么想的话那么就请准备好接受再教育吧。可能在某个地方某个人给你讲过尽量使用少的空间是件好事情。不错这种说法的确正确但是他忽略了事实中一个很重要的方面。 如果你编写的是运行在16K EPROMs下的嵌入式程序的话你有理由尽量少使用空间在这种环境下它能使你的程序更健壮。但是在 500MHz, 256MB的机器上写 Windows 程序如果你还是这么做它只会比你认为的“低效”的代码运行得更糟。 举例来说。字符串的大小被认为是影响效率的首要因素使字符串尽可能小可以提高效率反之则降低效率这是大家一贯的想法。但是这种想法是不对的精确的内存分配的后果要在程序运行了好几个小时后才能体现得出来那时程序的堆中将充满小片的内存它们太小以至于不能用来做任何事但是他们增加了你程序的内存用量增加了内存页面交换的次数当页面交换的次数增加到系统能够忍受的上限系统则会为你的程序分配更多的页面直到你的程序占用了所有的可用内存。由此可见虽然内存碎片是决定效率的次要因素但正是这些因素实际控制了系统的行为最终它损害了系统的可靠性这是令人无法接受的。 记住在 debug 模式下内存往往是精确分配的这是为了更好的排错。 假设你的应用程序通常需要连续工作好几个月。比如我常打开 VCWordPowerPointFrontpageOutlook ExpressForté AgentInternet Explorer和其它的一些程序而且通常不关闭它们。我曾经夜以继日地连续用 PowerPoint 工作了好几天反之如果你不幸不得不使用像 Adobe FrameMaker 这样的程序的话你将会体会到可靠性的重要这个程序机会每天都要崩溃4~6次每次都是因为用完了所有的空间并填满我所有的交换页面。所以精确内存分配是不可取的它会危及到系统的可靠性并引起应用程序崩溃。 按量子的倍数为字符串分配内存内存分配器就可以回收用过的内存块通常这些回收的内存块马上就可以被其它的 CString 对象重新用到这样就可以保证碎片最少。分配器的功能加强了应用程序用到的内存就能尽可能保持最小这样的程序就可以运行几个星期或几个月而不出现问题。 题外话很多年以前我们在 CMU 写一个交互式系统的时候一些对内存分配器的研究显示出它往往产生很多内存碎片。Jim Mitchell现在他在 Sun Microsystems 工作那时侯他创造了一种内存分配器它保留了一个内存分配状况的运行时统计表这种技术和当时的主流分配器所用的技术都不同且较为领先。当一个内存块需要被分割得比某一个值小的话他并不分割它因此可以避免产生太多小到什么事都干不了的内存碎片。事实上他在内存分配器中使用了一个浮动指针他认为与其让指令做长时间的存取内存操作还不如简单的忽略那些太小的内存块而只做一些浮动指针的操作。His observation was that the long-term saving in instructions by not having to ignore unusable small storage chunks far and away exceeded the additional cost of doing a few floating point operations on an allocation operation.他是对的。 永远不要认为所谓的“最优化”是建立在每一行代码都高速且节省内存的基础上的事实上高速且节省内存应该是在一个应用程序的整体水平上考虑的。在软件的整体水平上只使用最小内存的字符串分配策略可能是最糟糕的一种方法。 如果你认为优化是你在每一行代码上做的那些努力的话你应该想一想在每一行代码中做的优化很少能真正起作用。你可以看我的另一篇关于优化问题的文章《Your Worst Enemy for some thought-provoking ideas》。 记住 运算符只是一种特例如果你写成下面这样CString s SomeCString1 SomeCString2 SomeCString3 , SomeCString4;则每一个 的应用会造成一个新的字符串被创建和一次复制操作。 总结 以上是使用 CString 的一些技巧。我每天写程序的时候都会用到这些。CString 并不是一种很难使用的类但是 MFC 没有很明显的指出这些特征需要你自己去探索、去发现。 CString Class Members
ConstructionThe String as an ArrayAssignment/ConcatenationComparisonExtractionOther ConversionsSearchingArchive/DumpBuffer AccessWindows-Specific
Construction
CStringConstructs CString objects in various ways. The String as an Array
GetLengthReturns the number of characters in a CString object. For multibyte characters, counts each 8-bit character; that is, a lead and trail byte in one multibyte character are counted as two characters.IsEmptyTests whether a CString object contains no characters.EmptyForces a string to have 0 length.GetAtReturns the character at a given position.operator []Returns the character at a given position — operator substitution for GetAt.SetAtSets a character at a given position.operator LPCTSTR Directly accesses characters stored in a CString object as a C-style string. Assignment/Concatenation
operator Assigns a new value to a CString object.operator Concatenates two strings and returns a new string.operator Concatenates a new string to the end of an existing string. Comparison
operator , etc.Comparison operators (case sensitive).CompareCompares two strings (case sensitive).CompareNoCaseCompares two strings (case insensitive).CollateCompares two strings (case sensitive, uses locale-specific information).CollateNoCaseCompares two strings (case insensitive, uses locale-specific information). Extraction
MidExtracts the middle part of a string (like the Basic MID$ function).LeftExtracts the left part of a string (like the Basic LEFT$ function).RightExtracts the right part of a string (like the Basic RIGHT$ function).SpanIncludingExtracts a substring that contains only the characters in a set.SpanExcludingExtracts a substring that contains only the characters not in a set. Other Conversions
MakeUpperConverts all the characters in this string to uppercase characters.MakeLowerConverts all the characters in this string to lowercase characters.MakeReverseReverses the characters in this string.ReplaceReplaces indicated characters with other characters.RemoveRemoves indicated characters from a string.InsertInserts a single character or a substring at the given index within the string.DeleteDeletes a character or characters from a string.FormatFormat the string as sprintf does.FormatVFormats the string as vsprintf does.TrimLeftTrim leading whitespace characters from the string.TrimRightTrim trailing whitespace characters from the string.FormatMessageFormats a message string. Searching
FindFinds a character or substring inside a larger string.ReverseFindFinds a character inside a larger string; starts from the end.FindOneOfFinds the first matching character from a set. Archive/Dump
operator Inserts a CString object to an archive or dump context.operator Extracts a CString object from an archive. Buffer Access
GetBufferReturns a pointer to the characters in the CString.GetBufferSetLengthReturns a pointer to the characters in the CString, truncating to the specified length.ReleaseBufferReleases control of the buffer returned by GetBuffer.FreeExtraRemoves any overhead of this string object by freeing any extra memory previously allocated to the string.LockBufferDisables reference counting and protects the string in the buffer.UnlockBufferEnables reference counting and releases the string in the buffer. Windows-Specific
AllocSysStringAllocates a BSTR from CString data.SetSysStringSets an existing BSTR object with data from a CString object.LoadStringLoads an existing CString object from a Windows resource.AnsiToOemMakes an in-place conversion from the ANSI character set to the OEM character set.OemToAnsiMakes an in-place conversion from the OEM character set to the ANSI character set. CString Overview | Hierarchy Chart