CString 操作指南

·         原著:Joseph M. Newcomer

翻译:littleloach

原文出处:codeprojectCString Management


通过阅读本文你能学习怎么有效地使用 CString

  CString 是一种非常有用的数据类型。他们非常大程度上简化了MFC中的许多操作,使得MFC在做字符串操作的时候方便了非常多。不管怎样,使用CString有非常多特别的技巧,特别是对于纯C背景下走出来的程式员来说有点难以学习。这篇文章就来讨论这些技巧。
  使用CString能让你对字符串的操作更加直截了当。这篇文章不是CString的完全手册,但囊括了大部分常见基本问题。

这篇文章包括以下内容:

1.     CString 对象的连接

2.     格式化字符串(包括 int 型转化为 CString

3.     CString 型转化成 int

4.     CString 型和 char* 类型的相互转化

5.     char* 转化成 CString

6.     CString 转化成 char* 之一:使用LPCTSTR强制转化

7.     CString 转化成 char* 之二:使用CString对象的GetBuffer方法

8.     CString 转化成 char* 之三: 和控件的接口

9.     CString 型转化成 BSTR 型;

10.   BSTR 型转化成 CString 型;

11.   VARIANT 型转化成 CString 型;

12.   载入字符串表资源;

13.   CString 和临时对象;

14.   CString 的效率;

15.   总结

下面我分别讨论。

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操作,这样不会降低程式的效率吗?这种想法是多余的,你必须要了解编译器实际上做了什么,没有哪一个CC++编译器会把这种无聊的乘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));

4CString 型和 char* 类型的相互转化

  这是初学者使用 CString 时最常见的问题。有了 C++ 的帮助,非常多问题你不必深入的去考虑他,直接拿来用就行了,不过如果你不能深入了解他的运行机制,又会有非常多问题让你迷惑,特别是有些看起来没有问题的代码,却偏偏不能正常工作。
比如,你会奇怪为什么不能写向下面这样的代码呢:

CString graycat = "Gray" + "Cat";

或这样:

CString graycat("Gray" + "Cat");

  事实上,编译器将抱怨上面的这些尝试。为什么呢?因为针对CString LPCTSTR数据类型的各种各样的组合,“ +” 运算符被定义成一个重载操作符。而不是两个 LPCTSTR 数据类型,他是底层数据类型。你不能对基本数据(如 intchar 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 操作,然后将他赋值给一个 CString

char 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 的值传递给一个控件,比如,CTreeCtrlMFC为我们提供了非常多便利来重载这个操作,不过在大多数情况下,你使用原始形式的更新,因此需要将墨某个串指针存储到 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 类型的变量,这种操作在CC++中是被禁止的。你不能用这种方法来滥用常量指针和非常量指针概念,否则,会扰乱编译器的优化机制,使之不知怎么优化你的程式。比如,如果你这么做:

const int i = ...;

//... do lots of stuff

... = a[i]; // usage 1

// ... lots more stuff

... = a[i]; // usage 2

  那么,编译器会以为既然 i const ,所以 usage1usage2的值是相同的,并且他甚至能事先计算好 usage1 处的 a[i] 的地址,然后保留着在后面的 usage2 处使用,而不是重新计算。如果你按如下方式写的话:

const int i = ...;

int * p = &i;

//... do lots of stuff

... = a[i]; // usage 1

// ... lots more stuff

(*p)++; // mess over compiler’’s 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 转化成 BSTR

CString 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);             // don’’t 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 可能返回(通过其一个参数)一个包含有BYTEWORDfloatdoubledateBSTR 等等 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 内存永远也不可能存在的,如果你试图访问0x000000000x0000FFFF之间的内存,将会引发一个内存越界错误。所以说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*LPCSTRGetBuffer 返回的值是正确的,不过当我把他赋值给 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(Name+1) 得到的实际结果对于 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 个字符串连接起来效率多低啊。事实上,在非常多情况下根本就不必复制源字符串(在 += 操作符左边的字符串)。
  在 VC++6.0 中,Release 模式下,所有的 CString 中的缓存都是按预定义量子分配的。所谓量子,即确定为 64128256 512 字节。这意味着除非字符串非常长,连接字符串的操作实际上就是 strcat 经过优化后的版本(因为他知道本地的字符串应该在什么地方结束,所以不必寻找字符串的结尾;只需要把内存中的数据拷贝到指定的地方即可)加上重新计算字符串的长度。所以他的执行效率和纯 C 的代码是相同的,不过他更容易写、更容易维护和更容易理解。
  如果你还是不能确定究竟发生了怎样的过程,请看看 CString 的原始码,strcore.cpp,在你 vc98的安装目录的 mfcsrc 子目录中。看看 ConcatInPlace 方法,他被在所有的 += 操作符中调用。

啊哈!难道 CString 真的这么"高效"吗?比如,如果我创建

CString cat("Mew!");

  然后我并不是得到了一个高效的、精简的5个字节大小的缓冲区(4个字符加一个结束字符),系统将给我分配64个字节,而其中59个字节都被浪费了。
  如果你也是这么想的话,那么就请准备好接受再教育吧。可能在某个地方某个人给你讲过尽量使用少的空间是件好事情。不错,这种说法的确正确,不过他忽略了事实中一个非常重要的方面。
  如果你编写的是运行在16K EPROMs下的嵌入式程式的话,你有理由尽量少使用空间,在这种环境下,他能使你的程式更健壮。不过在 500MHz, 256MB的机器上写视窗系统程式,如果你还是这么做,他只会比你认为的低效的代码运行得更糟。
  举例来说。字符串的大小被认为是影响效率的首要因素,使字符串尽可能小能提高效率,反之则降低效率,这是大家一贯的想法。不过这种想法是不对的,精确的内存分配的后果要在程式运行了好几个小时后才能体现得出来,那时,程式的堆中将充满小片的内存,他们太小以至于不能用来做所有事,不过他们增加了你程式的内存用量,增加了内存页面交换的次数,当页面交换的次数增加到系统能够忍受的上限,系统则会为你的程式分配更多的页面,直到你的程式占用了所有的可用内存。由此可见,虽然内存碎片是决定效率的次要因素,但正是这些因素实际控制了系统的行为,最终,他损害了系统的可靠性,这是令人无法接受的。
  记住,在 debug 模式下,内存往往是精确分配的,这是为了更好的排错。
  假设你的应用程式通常需要连续工作好几个月。比如,我常打开 VC++WordPowerPointFrontpageOutlook 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;

则每一个 + 的应用会造成一个新的字符串被创建和一次复制操作。


 
VC++类型转换整理[Z]

2008-05-22 09:09

Maxview(肥虫虫)

根据我以往的习惯,还是在开始正题之前说几句废话:  
   
1、个人认为学习编程难的就在背!你背它三、五千个函数的用法,之后开始编程,肯定容易的手到擒来;  
   
2、回复一个朋友的贴子的时候,有人说我的语句有问题,我也不清楚有没有问题,因而这篇文章一方面让大家当作速查手册,另一方面希望大家指出其中可能存在的问题;  
   
开始!  
   
1、CString   to   char*  
经过类型强制转换,可以将CString类型转换成char*,例如:  
CString   cStr   =   "Hello,world!";  
char*   zStr   =   (char*)(LPCTSTR)cStr;  
   
2、char*   to   CString  
char*类型可以直接给CString,完成自动转换,例如:  
char*   zStr   =   "Hello,world!";  
CString   cStr   =   zStr;  
   
3、CString   to   LPCSTR  
将CString转换成LPCSTR,需要获得CString的长度,例如:  
CString   cStr   =   _T("Hello,world!");  
int   nLen   =   cStr.GetLength();  
LPCSTR   lpszBuf   =   cStr.GetBuffer(nLen);  
   
4、CString   to   LPSTR  
这个和第3个技巧是一样的,例如:  
CString   cStr   =   _T("Hello,world!");  
int   nLen   =   str.GetLength();  
LPSTR   lpszBuf   =   str.GetBuffer(nLen);  
   
5、Char[]   to   int  
将字符串类型转换成整数型,可以使用atoi函数,例如:  
char   c[10];  
int   n;  
n   =   atoi(c);  
   
6、Char[]   to   float  
和第5个技巧一样,使用atof()函数可以转换成float型,例如:  
char   c[10];  
float   f;  
f   =   atof(c);  
   
7、Char*   to   int  
和第5个技巧完全一样,例如:  
char   *str   =   "100";    
int   i;  
i   =   atoi(str);  


         刚接触VC编程的朋友往往对许多数据类型的转换感到迷惑不解,本文将介绍一些常用数据类型的使用。  
   
我们先定义一些常见类型变量借以说明  
   
int   i   =   100;  
long   l   =   2001;  
float   f=300.2;  
double   d=12345.119;  
char   username[]="女侠程佩君";  
char   temp[200];  
char   *buf;  
CString   str;  
_variant_t   v1;  
_bstr_t   v2;   

kxcc_sx(开心虫虫)
    
一、其它数据类型转换为字符串  
   
短整型(int)  
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制  
itoa(i,temp,2);   ///按二进制方式转换    
长整型(long)  
ltoa(l,temp,10);  
   
   
二、从其它包含字符串的变量中获取指向该字符串的指针  
   
CString变量  
str   =   "2008北京奥运";  
buf   =   (LPSTR)(LPCTSTR)str;    
BSTR类型的_variant_t变量  
v1   =   (_bstr_t)"程序员";  
buf   =   _com_util::ConvertBSTRToString((_bstr_t)v1);  
   
三、字符串转换为其它数据类型  
strcpy(temp,"123");    
   
短整型(int)  
i   =   atoi(temp);    
长整型(long)  
l   =   atol(temp);    
浮点(double)  
d   =   atof(temp);  
   
四、其它数据类型转换到CString  
使用CString的成员函数Format来转换,例如:  
   
整数(int)  
str.Format("%d",i);    
浮点数(float)  
str.Format("%f",i);    
字符串指针(char   *)等已经被CString构造函数支持的数据类型可以直接赋值  
str   =   username;  
   
五、BSTR、_bstr_t与CComBSTR  
   
CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。  
char   *转换到BSTR可以这样:   BSTR   b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文件comutil.h  
反之可以使用char   *p=_com_util::ConvertBSTRToString(b);  
   
   
六、VARIANT   、_variant_t   与   COleVariant  
   
VARIANT的结构可以参考头文件VC98\Include\OAIDL.H中关于结构体tagVARIANT的定义。  
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:  
VARIANT   va;  
int   a=2001;  
va.vt=VT_I4;///指明整型数据  
va.lVal=a;   ///赋值  
   
对于不马上赋值的VARIANT,最好先用Void   VariantInit(VARIANTARG   FAR*   pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:  
   
unsigned   char   bVal;   VT_UI1    
short   iVal;   VT_I2    
long   lVal;     VT_I4      
float   fltVal;     VT_R4    
double   dblVal;     VT_R8      
VARIANT_BOOL   boolVal;     VT_BOOL    
SCODE   scode;     VT_ERROR    
CY   cyVal;     VT_CY    
DATE   date;     VT_DATE    
BSTR   bstrVal;     VT_BSTR    
IUnknown   FAR*   punkVal;     VT_UNKNOWN    
IDispatch   FAR*   pdispVal;     VT_DISPATCH    
SAFEARRAY   FAR*   parray;     VT_ARRAY|*    
unsigned   char   FAR*   pbVal;     VT_BYREF|VT_UI1    
short   FAR*   piVal;     VT_BYREF|VT_I2    
long   FAR*   plVal;     VT_BYREF|VT_I4    
float   FAR*   pfltVal;     VT_BYREF|VT_R4    
double   FAR*   pdblVal;   VT_BYREF|VT_R8    
VARIANT_BOOL   FAR*   pboolVal;   VT_BYREF|VT_BOOL    
SCODE   FAR*   pscode;     VT_BYREF|VT_ERROR    
CY   FAR*   pcyVal;     VT_BYREF|VT_CY    
DATE   FAR*   pdate;   VT_BYREF|VT_DATE    
BSTR   FAR*   pbstrVal;     VT_BYREF|VT_BSTR    
IUnknown   FAR*   FAR*   ppunkVal;     VT_BYREF|VT_UNKNOWN    
IDispatch   FAR*   FAR*   ppdispVal;   VT_BYREF|VT_DISPATCH    
SAFEARRAY   FAR*   FAR*   pparray;     VT_ARRAY|*    
VARIANT   FAR*   pvarVal;     VT_BYREF|VT_VARIANT    
void   FAR*   byref;     VT_BYREF    
   
_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。  
例如:  
long   l=222;  
ing   i=100;  
_variant_t   lVal(l);  
lVal   =   (long)i;  
   
COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:  
COleVariant   v3   =   "字符串",   v4   =   (long)1999;  
CString   str   =(BSTR)v3.pbstrVal;  
long   i   =   v4.lVal;  
   
七、其它  
   
对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:  
LPARAM   lParam;  
WORD   loValue   =   LOWORD(lParam);///取低16位  
WORD   hiValue   =   HIWORD(lParam);///取高16位    
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:  
WORD   wValue;  
BYTE   loValue   =   LOBYTE(wValue);///取低8位  
BYTE   hiValue   =   HIBYTE(wValue);///取高8位    
后记:本文匆匆写成,错误之处在所难免,欢迎来信指正。  

常用函数和范例:

 

☆改变大小写:

 

CString::MakeUpperCString::MakeLower两个成员函数(不带参数)能使整个字符串变成大/小写字母。

 

例:       str1 = hello;

 

               str1.MakeUpper();

 

               afxDump << str1;         // 输出结果是”HELLO”;

 

☆反转:void CString::MakeReverse();

 

☆从.rc文件读入字符串:

 

CString::LoadString函数把传入的字符串资源ID对应的字符串读入到CString对象中。如果成功就返回非零值。

 

       BOOL bResult = LoadString(IDS_FILENOTFOUND);

 

☆子串操作

 

→去掉字符串左边空格:str1.TrimLeft();

 

→去掉字符串右边空格:str1.TrimRight();

 

→获得指定位置字符:char a = str1.GetAt(3); 相应的有CString::SetAt函数,修改指定位置字符。

 

→删除字符串中所有指定字符:

 

       str1 = “Hello test”;

 

       str1.Remove(‘t’);

 

       afxDump << str1;         //输出”Hello es”;

 

→删除指定位置指定长度的子串:

 

       str1 = “Hello test”;

 

       str1.Delete(3, 2);           //第一个参数为index(从零开始)

 

                                          //第二个参数是长度

 

       afxDump << str1;         //输出”Hel test”;

 

→清空对象的内容:

 

void CString::Empty();

 

→查找子串:

 

CString::Find函数有四个重载,可以进行字符和字串的搜索,同时还可以指定搜索的起始位置,并返回第一次查找到的index

 

int Find( TCHAR ch ) const;

 

int Find( LPCTSTR lpszSub ) const;

 

int Find( TCHAR ch, int nStart ) const;

 

int Find( LPCTSTR pstr, int nStart ) const;

 

CString::ReverseFind是返回字符串中最后一个匹配字符的index,与Find函数查找方向正好相反,可惜只有一种重载:

 

int ReverseFind( TCHAR ch ) const;

 

CString::FindOneof查找的是第一个与指定字符串中任意一个匹配字符的index。(好像有点绕口,看看例子就明白了)

 

       str1 = “Hello test”;

 

       int j = str1.Find(“el”);

 

       afxDump << “j=” << j << “\n”;

 

       int k = str1.Find(‘e’, 3);

 

       afxDump << “k=” << k << “\n”;

 

       int l = str1.ReverseFind(‘t’);

 

       afxDump << “l=” << l << “\n”;

 

       int m = str1.ReverseFind(‘t’);

 

       afxDump << “m=” << m << “\n”;

 

       int n = str1. FindOneOf(“stuv”);

 

       afxDump << “n=” << n << “\n”;

 

输出结果:

 

       j=1

 

       k=7

 

       l=9

 

       m=9

 

       n=6

 

→字串截断:CString::LeftCString::Right函数都只带一个参数,并且都返回一个CString对象,作用是截取左/右边指定长度的子串。CString::Mid函数第一个参数指定位置,第二个参数指定长度。这几个都是常用的函数,就不写例子了

 

→获得Buffer

 

经常有人问到CString对象和char *的转换问题,除了前面说到的强制转化,就是用这个了

 

LPTSTR GetBufferSetLength( int nNewLength );使用返回的指针可以直接修改CString对象的内容,不过有两点要注意,一是如果指定长度比原CString长度短(截断)请记得在后面补’\0’,二是在调用CString对象的任何其它成员函数前请一定记得ReleaseBuffer,也许不用似乎并没有出错,但是说不定就是大隐患的根源。

 

CString::SpanExcluding函数

 

以前回答过一个把CString对象分离子串的问题,现在想想,如果当时用这个函数的话,将使多么的方便。函数原型:

 

CString SpanExcluding( LPCTSTR lpszCharSet ) const;

 

它查找CString对象中与lpszCharSet串中任意匹配的第一个字符,并返回一个CString对象,该对象的内容是原来对象从起始位置到查找到字符的前一个字符的部分。这在分离用分割符(逗号空格之类)隔开的子串的时候将十分方便:

 

       str1 = “Hello test”;

 

       str2 = str1.SpanExcluding(“ ,”);

 

       afxDump << str2;         //输出”Hello

 

同时,还有一个很方便的函数:CString::SpanIncluding,函数原型:

 

CString SpanIncluding( LPCTSTR lpszCharSet ) const;

 

它返回对象中前若干个字符,这些字符都必须在lpszCharSet之中:

 

       str1 = “Hello test”;

 

       str2 = str1.SpanIncluding(“ABCDEFGHIJK”);

 

       afxDump << str2;         //输出”H

 

→插入子串:用CString::Insert可以插入字符或者字串到指定位置

 

       str1 = “Hello test”;

 

       str1.Insert(2,“ABCD”);

 

       afxDump << str1;         //输出”HeABCDllo test

 

→替换:CString::Replace的作用是将原来对象中的所有匹配相替换指定字符/子串。有两个重载原型:

 

int Replace( TCHAR chOld, TCHAR chNew );

 

int Replace( LPCTSTR lpszOld, LPCTSTR lpszNew );

 

CString对象的属性操作:这些都很常用了,简要说明之

 

int GetLength( ) const;          //获得buffer的长度

 

BOOL IsEmpty( ) const;              //判断CString对象内容是否为空

 

int Compare( LPCTSTR lpsz ) const;   //lpszASCII码比较

 

int CompareNoCase( LPCTSTR lpsz ) const;             //lpszASCII码比较,忽略大小写

 

CString::Format             /*用来格式化对象。切记不要把对象本身放到Format函数的参数中去了*/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值