正因为如此,一个这样的内存块可被多个CString所引用,例如下列代码:
正因为采用了这样的好机制,使得CString在大量拷贝时,不仅效率高,而且分配内存少。
假如我想直接通过指针去修改数据的话,那怎样办呢?就是用GetBuffer(...).看下述代码:
下面演示一段代码执行过程
通过观察上面执行过程,我们会发现CString虽然可以多个对象指向同一引用内块存,但是它们在进行各种拷贝、赋值及改变串内容时,它的处理是很智能并且非常安全的,完全做到了互不干涉、互不影响。当然必须要求你的代码使用正确恰当,特别是实际使用中会有更复杂的情况,如做函数参数、引用、及有时需保存到CStringList当中,如果哪怕有一小块地方使用不当,其结果也会导致发生不可预知的错误
5 FreeExtra()的作用
6 Format(...) 与 FormatV(...)
7 LockBuffer() 与 UnlockBuffer()
No other string can get a reference to the data in the locked string, even if that string is assigned to the locked string.
8 CString 只是处理串吗?
8 AllocSysString()与SetSysString(BSTR*)
9 参数的安全检验
10 CString的异常处理
附上一点CString的类型转化:
1、CString to char*
2、char* to CString
3、CString to LPCSTR
4、CString to LPSTR
5、Char[] to int
6、Char[] to float
7、Char* to int
因此为了减少频繁的申请内存或者释放内存,CString会先申请一个大的内存块用来存放字符串。这样,以后当字符串长度增长时,如果增加的总长度不超过预先申请的内存块的长度,就不用再申请内存。当增加后的字符串长度超过预先申请的内存时,CString先释放原先的内存,然后再重新申请一个更大的内存块。同样的,当字符串长度减少时,也不释放多出来的内存空间。而是等到积累到一定程度时,才一次性将多余的内存释放。
1 CString实现的机制.
CString是通过“引用”来管理串的,“引用”这个词我相信大家并不陌生,象Window内核对象、COM对象等都是通过引用来实现的。而CString也是通过这样的机制来管理分配的内存块。实际上CString对象只有一个指针成员变量,所以任何CString实例的长度只有4字节.
即: int len = sizeof(CString);//len等于4
这个指针指向一个相关的引用内存块,如图: CString str("abcd");
___
____________ | |
| | | |
| 0x04040404 | | | head部,为引用内存块相关信息
|____________| | |
str |___|
|'a'| 0x40404040
|'b'|
|'c'|
|'d'|
| 0 |
CString str("abcd");
CString a = str;
CString b(str);
CString c;
c = b;
上面代码的结果是:上面四个对象(str,a,b,c)中的成员变量指针有相同的值,都为0x40404040.而这块内存块怎么知道有多少个CString引用它呢?同样,它也会记录一些信息。如被引用数,串长度,分配内存长度。
这块引用内存块的结构定义如下:
struct CStringData
{
long nRefs; //表示有多少个CString 引用它. 4
int nDataLength; //串实际长度. 4
int nAllocLength; //总共分配的内存长度(不计这头部的12字节). 4
};
由于有了这些信息,CString就能正确地分配、管理、释放引用内存块。
如果你想在调试程序的时候获得这些信息。可以在Watch窗口键入下列表达式:
(CStringData*)((CStringData*)(this->m_pchData)-1)或
(CStringData*)((CStringData*)(str.m_pchData)-1)//str为指CString实例
2 LPCTSTR 与 GetBuffer(int nMinBufLength)
所以在代码中有时可以隐式转换,而有时却需强制转制。如:
CString str;
const char* p = (LPCTSTR)str;
//假设有这样的一个函数,Test(const char* p); 你就可以这样调用
Test(str);//这里会隐式转换为LPCTSTR
CString str("abcd");
char* p = (char*)(const char*)str;
p[2] = 'z';
其实,也许有这样的代码后,你的程序并没有错,而且程序也运行得挺好。但它却是非常危险的。再看
CString str("abcd");
CString test = str;
....
char* p = (char*)(const char*)str;
p[2] = 'z';
strcpy(p, "akfjaksjfakfakfakj");//这下完蛋了
你知道此时,test中的值是多少吗?答案是"abzd".它也跟着改变了,这不是你所期望发生的。但为什么会这样呢?你稍微想想就会明白,前面说过,因为CString是指向引用块的,str与test指向同一块地方,当你p[2]='z'后,当然test也会随着改变。所以用它做LPCTSTR做转换后,你只能去读这块数据,千万别去改变它的内容。
CString str("abcd");
CString test = str;
....
char* p = str.GetBuffer(20);
p[2] = 'z'; // 执行到此,现在test中值却仍是"abcd"
strcpy(p, "akfjaksjfakfakfakj"); // 执行到此,现在test中值还是"abcd"
为什么会这样?其实GetBuffer(20)调用时,它实际上另外建立了一块新内块存,并分配20字节长度的buffer,而原来的内存块引用计数也相应减1. 所以执行代码后str与test是指向了两块不同的地方,所以相安无事。
char* p = NULL;
const char* q = NULL;
{
CString str = "abcd";
q = (LPCTSTR)str;
p = str.GetBuffer(20);
AfxMessageBox(q);// 合法的
strcpy(p, "this is test");//合法的,
}
AfxMessageBox(q);// 非法的,可能完蛋
strcpy(p, "this is test");//非法的,可能完蛋
这里要说的就是,当返回这些指针后, 如果CString对象生命结束,这些指针也相应无效。
void Test()
{
CString str("abcd");//str指向一引用内存块(引用内存块的引用计数为1,
长度为4,分配长度为4)
CString a;//a指向一初始数据状态,
a = str; //a与str指向同一引用内存块(引用内存块的引用计数为2,
长度为4,分配长度为4)
CString b(a);//a、b与str指向同一引用内存块(引用内存块的引用
计数为3,长度为4,分配长度为4)
{
LPCTSTR temp = (LPCTSTR)a;//temp指向引用内存块的串首地址。
(引用内存块的引用计数为3,长度为4,分配长度为4)
CString d = a; //a、b、d与str指向同一引用内存块(引用内存块的引用计数为4, 长度为4,分配长度为4)
b = "testa"; //这条语句实际是调用CString::operator=(CString&)函数。
b指向一新分配的引用内存块。(新分配的引用内存块的
引用计数为1,长度为5,分配长度为5)
//同时原引用内存块引用计数减1. a、d与str仍指向原
引用内存块(引用内存块的引用计数为3,长度为4,分配长度为4)
}//由于d生命结束,调用析构函数,导至引用计数减1(引用内存
块的引用计数为2,长度为4,分配长度为4)
LPTSTR temp = a.GetBuffer(10);//此语句也会导致重新分配新内存块。
temp指向新分配引用内存块的串首地址(新
分配的引用内存块的引用计数为1,长度
为0,分配长度为10)
//同时原引用内存块引用计数减1. 只有str仍
指向原引用内存块(引用内存块的引用计数为1,
长度为4,分配长度为4)
strcpy(temp, "temp"); //a指向的引用内存块的引用计数为1,长度为0,分配长度为10
a.ReleaseBuffer();//注意:a指向的引用内存块的引用计数为1,长度为4,分配长度为10
}
//执行到此,所有的局部变量生命周期都已结束。对象str a b 各自调用自己的析构构
//函数,所指向的引用内存块也相应减1
//注意,str a b 所分别指向的引用内存块的计数均为0,这导致所分配的内存块释放
看这段代码
(1) CString str("test");
(2) LPTSTR temp = str.GetBuffer(50);
(3) strcpy(temp, "there are 22 character");
(4) str.ReleaseBuffer();
(5) str.FreeExtra();
上面代码执行到第(4)行时,大家都知道str指向的引用内存块计数为1,长度为22,分配长度为50. 那么执行str.FreeExtra()时,它会释放所分配的多余的内存。(引用内存块计数为1,长度为22,分配长度为22)
这条语句在使用中是最容易出错的。因为它最富有技巧性,也相当灵活。在这里,我没打算对它细细分析,实际上sprintf(...)怎么用,它就怎么用。我只提醒使用时需注意一点:就是它的参数的特殊性,由于编译器在编译时并不能去校验格式串参数与对应的变元的类型及长度。所以你必须要注意,两者一定要对应上,
否则就会出错。如:
CString str;
int a = 12;
str.Format("first:%l, second: %s", a, "error");//result?试试
顾名思议,这两个函数的作用就是对引用内存块进行加锁及解锁。
但使用它有什么作用及执行过它后对CString串有什么实质上的影响。其实挺简单,看下面代码:
(1) CString str("test");
(2) str.LockBuffer();
(3) CString temp = str;
(4) str.UnlockBuffer();
(5) str.LockBuffer();
(6) str = "error";
(7) str.ReleaseBuffer();
执行完(3)后,与通常情况下不同,temp与str并不指向同一引用内存块。你可以在watch窗口用这个表达式(CStringData*)((CStringData*)(str.m_pchData)-1)看看。
其实在msdn中有说明:
While in a locked state, the string is protected in two ways:
The locked string will never reference another string, even if that other string is copied to the locked string.
不对,CString不只是能操作串,而且还能处理内存块数据。功能完善吧!看这段代码
char p[20];
for(int loop=0; loop<sizeof(p); loop++)
{
p[loop] = 10-loop;
}
CString str((LPCTSTR)p, 20);
char temp[20];
memcpy(temp, str, str.GetLength());
str完全能够转载内存块p到内存块temp中。所以能用CString来处理二进制数据
这两个函数提供了串与BSTR的转换。使用时须注意一点:当调用AllocSysString()后,须调用它SysFreeString(...)
在MFC中提供了多个宏来进行参数的安全检查,如:ASSERT. 其中在CString中也不例外,有许多这样的参数检验,其实这也说明了代码的安全性高,可有时我们会发现这很烦,也导致Debug与Release版本不一样,如有时程序Debug通正常,而Release则程序崩溃;而有时恰相反,Debug不行,Release行。其实我个人认为,我们对CString的使用过程中,应力求代码质量高,不能在Debug版本中出现任何断言框,哪怕release运行似乎
看起来一切正常。但很不安全。如下代码:
(1) CString str("test");
(2) str.LockBuffer();
(3) LPTSTR temp = str.GetBuffer(10);
(4) strcpy(temp, "error");
(5) str.ReleaseBuffer();
(6) str.UnlockBuffer();//执行到此时,Debug版本会弹出错框
我只想强调一点:只有分配内存时,才有可能导致抛出CMemoryException.
同样,在msdn中的函数声明中,注有throw( CMemoryException)的函数都有重新分配或调整内存的可能。
经过类型强制转换,可以将CString类型转换成char*,例如:
CString cStr = "Hello,world!";
char* zStr = (char*)(LPCTSTR)cStr;
char*类型可以直接给CString,完成自动转换,例如:
char* zStr = "Hello,world!";
CString cStr = zStr;
将CString转换成LPCSTR,需要获得CString的长度,例如:
CString cStr = _T("Hello,world!");
int nLen = cStr.GetLength();
LPCSTR lpszBuf = cStr.GetBuffer(nLen);
这个和第3个技巧是一样的,例如:
CString cStr = _T("Hello,world!");
int nLen = str.GetLength();
LPSTR lpszBuf = str.GetBuffer(nLen);
将字符串类型转换成整数型,可以使用atoi函数,例如:
char c[10];
int n;
n = atoi(c);
和第5个技巧一样,使用atof()函数可以转换成float型,例如:
char c[10];
float f;
f = atof(c);
和第5个技巧完全一样,例如:
char *str = "100";
int i;
i = atoi(str);
相关栏目:
您当前位置:
返回顶部