CStrin g的Get Buffe r用法,GetBuf fer本质,GetBuf fer常见问题解决
方法
char *GetBuf fer(n)
当n大于0时,是为CString变量分配一个长度为n的字节数组,返回值是这个数组的地址
当n等于0时,返回CString变量本身拥有的字符串数组的头
Releas eBuff er一般用在GetBuffer,因为在调用了GetBuffer后变量本身会给自己上锁,于是
所有能改变自身值的函数都不能用(如果Left,Mid),要用ReleaseBu ffer解锁一.函数原型
CStrin g::GetBuf fer
LPTSTR GetBuf fer( int nMinBu fLeng th );
throw( CMemor yExce ption );
Return Value
An LPTSTR pointe r to the object’s (null-termin ated) charac ter buffer.
Parame ters
nMinBu fLeng th
The minimu m size of the charac ter buffer in charac ters. This valuedoes not includ e spacefor a null termin ator.
Remark s
Return s a pointe r to the intern al charac ter buffer for the CStrin g object. The return ed LPTSTR is not constand thus allows direct modifi catio n of CStrin g conten ts.
If you use the pointe r return ed by GetBuf fer to change the string conten ts, you must call Releas eBuff er before usingany otherCStrin g member functi ons.
二.函数作用及使用范围
对一个CString变量,你可以使用的唯一合法转换符是LPCTSTR,直接转换成非常量指针(LPTSTR-[const] char*)是错误的。正确的得到一个指向缓冲区的非常量指针的方法是调用GetBuff er()方法。
GetBuf fer()主要作用是将字符串的缓冲区长度锁定,releas eBuff er则是解除锁定,使得CString对象在以后的代码中继续可以实现长度自适应增长的功能。
CStrin g ::GetBuf fer有两个重载版本:
LPTSTR GetBuf fer( );LPTSTR GetBuf fer(int nMinBu fferL ength);
在第二个版本中,当设定的长度小于原字符串长度时,nMinBu fLeng th = nOldLe n,该参数会被忽
略,不分配内存,指向原CString;当设定的长度大于原字符串本身的长度时就要重新分配(reallo cate)一块比较大的空间出来。而调用第一个版本时,应如通过传入0来调用第二个版本一样。
是否需要在G etBuf er后面调用ReleaseBu ffer(),是根据你的后面的程序是否需要继续使用该
字符串变量,并且是否动态改变其长度而定的。如果你GetBuffe r以后程序自函数就退出,局部变量都不存在了,调用不调用R eleas eBuff er没什么意义了。
最典型的应用就是读取文件:
CFilefile;
/
/ FILE_N AME 为实现定义好的文件名称cstring转为int
if(file.Open(FILE_N AME,CFile::modeRe ad))
{
CStrin g szCont ent;
int nFileL ength = file.GetLen gth();
file.Read(szCont ent.GetBuf fer(nFileL ength),nFileL ength);
szCont ent.Releas eBuff er();
// 取得文件內容放在szConten t中,我们之后可以对其操作
}
三.测试
以下就CString::GetBuf fer,做简单测试:
测试1:
// exampl e for CStrin g::GetBuf fer
#includ e <stdio.h>
#includ e <afx.h>
void main(void)
{
CStrin g s( "abcd" );
printf("(1)before GetBuf fer:\n");
printf("CStrin g s.length=%d\n",s.GetLen gth());
printf("CStrin g s=%s\n",s);
LPTSTR p = s.GetBuf fer( 2 );
printf("(2)afterGetBuf fer and before Releas eBuff er:\n");
printf("LPTSTR p=%s\n",p);
printf("p.length=%d\n",strlen(p));
printf("CStrin g s=%s\n",s);
printf("CStrin g s.length=%d\n",s.GetLen gth());
s.Releas eBuff er( );
printf("(3)afterReleas eBuff er:\n");
printf("LPTSTR p=%s\n",p);
printf("p.length=%d\n",strlen(p));
printf("CStrin g s=%s\n",s);
printf("CStrin g s.length=%d\n",s.GetLen gth());
}
测试结果1:
(1)before GetBuf fer:
CStrin g s.length=4
CStrin g s=abcd
(2)afterGetBuf fer and before Releas eBuff er:
LPTSTR p=abcd
p.length=4
CStrin g s=abcd
CStrin g s.length=4
(3)afterReleas eBuff er:
LPTSTR p=abcd
p.length=4
CStrin g s=abcd
CStrin g s.length=4
Pressany key to contin ue
测试2:
将LPTSTR p = s.GetBuf fer( 2 ); 修改为:LPTSTR p = s.GetBuf fer( 10 ); 测试结果同1。
测试3:
在测试二的L PTSTR p = s.GetBuf fer( 10 );后添加 p[5]='f';
测试结果同1。
测试4:
将测试三的p[5]='f';修改为p[4]='e';
测试结果4:
(1)before GetBuf fer:
CStrin g s.length=4
CStrin g s=abcd
(2)afterGetBuf fer and before Releas eBuff er:
LPTSTR p=abcde屯屯?
p.length=10
CStrin g s=abcde屯屯?
CStrin g s.length=4
(3)afterReleas eBuff er:
LPTSTR p=abcde屯屯?
p.length=10
CStrin g s=abcde屯屯?
CStrin g s.length=10
Pressany key to contin ue
很显然(2)afterGetBuf fer and before Releas eBuff er:中 CStrin g s.length=4结果有问题。
注意:以上测试是在_MBCS环境下,如果换成_UNICOD E则结果有可能不同。
LPTSTR CStrin g::GetBuf fer(int nMinBu fLeng th)
{
ASSERT(nMinBu fLeng th > = 0);
if (GetDat a()-> nRefs > 1 || nMinBu fLeng th > GetDat a()->
nAlloc Lengt h)
{
#ifdef _DEBUG
// give a warnin g in case locked string become s unlock ed
if (GetDat a() != _afxDa taNil && GetDat a()-> nRefs < 0)
TRACE0( "Warnin g: GetBuf fer on locked CStrin g create s unlock ed
CStrin g!\n ");
#endif
// we have to grow the buffer
CStrin gData* pOldDa ta = GetDat a();
int nOldLe n = GetDat a()-> nDataL ength; // AllocB uffer will trompit
if (nMinBu fLeng th < nOldLe n)
nMinBu fLeng th = nOldLe n;
AllocB uffer(nMinBu fLeng th);
memcpy(m_pchD ata, pOldDa ta-> data(), (nOldLe n+1)*sizeof(TCHAR));
GetDat a()-> nDataL ength = nOldLe n;
CStrin g::Releas e(pOldDa ta);
}
ASSERT(GetDat a()-> nRefs <= 1);
// return a pointe r to the charac ter storag e for this string ASSERT(m_pchD ata != NULL);
return m_pchD ata;
}
void CStrin g::Releas eBuff er(int nNewLe ngth)
{
CopyBe foreW rite(); // just in case GetBuf fer was not called
if (nNewLe ngth == -1)
nNewLe ngth = lstrle n(m_pchD ata); // zero termin ated
ASSERT(nNewLe ngth <= GetDat a()-> nAlloc Lengt h);
GetDat a()-> nDataL ength = nNewLe ngth;
m_pchD ata[nNewLe ngth] = '\0 ';
}
=============
看了很多人写的程序,包括我自己写的一些代码,发现很大的一部分bug是关于MF C类中的
C Strin g的错误用法的.出现这种错误的原因主要是对CString的实现机制不是太了解。
CStrin g是对于原来标准c中字符串类型的一种的包装。因为,通过很长时间的编程,我们发现,很多程序的b ug多和字符串有关,典型的有:缓冲溢出、内存泄漏等。而且这些bug都是致命的,会造
成系统的瘫痪。因此c++里就专门的做了一个类用来维护字符串指针。标准c++里的字符串类是string,在microsoftMFC类库中使用的是C Strin g类。通过字符串类,可以大大的避免c中的关于字符串指
针的那些问题。
这里我们简单的看看Microso ft MFC中的C Strin g是如何实现的。当然,要看原理,直接把它的代码拿过来分析是最好的。MFC里的关于CString的类的实现大部分在strcore.cpp中。
CStrin g就是对一个用来存放字符串的缓冲区和对施加于这个字符串的操作封装。也就是说,CStrin g里需要有一个用来存放字符串的缓冲区,并且有一个指针指向该缓冲区,该指针就是LPTSTR m_pchD ata。但是有些字符串操作会增建或减少字符串的长度,因此为了减少频繁的申请内
存或者释放内存,CStrin g会先申请一个大的内存块用来存放字符串。这样,以后当字符串长度增长
时,如果增加的总长度不超过预先申请的内存块的长度,就不用再申请内存。当增加后的字符串长
度超过预先申请的内存时,CStrin g先释放原先的内存,然后再重新申请一个更大的内存块。同样的,当字符串长度减少时,也不释放多出来的内存空间。而是等到积累到一定程度时,才一次性将多余的内存释放。
还有,当使用一个C Strin g对象a来初始化另一个CString对象b时,为了节省空间,新对象b并不分配空间,它所要做的只是将自己的指针指向对象a的那块内存空间,只有当需要修改对象a或者b
中的字符串时,才会为新对象b申请内存空间,这叫做写入复制技术(CopyBe foreW rite)。
这样, 仅仅通过一个指针就不能完整的描述这块内存的具体情况,需要更多的信息来
描述。
首先,需要有一个变量来描述当前内存块的总的大小。
其次,需要一个变量来描述当前内存块已经使用的情况。也就是当前字符串的长度
另外,还需要一个变量来描述该内存块被其他CString引用的情况。有一个对象引用该内存
块,就将该数值加一。
CStrin g中专门定义了一个结构体来描述这些信息:
struct CStrin gData
{
long nRefs; // refere nce count
int nDataL ength; // length of data (includ ing termin ator)
int nAlloc Lengt h; // length of alloca tion
// TCHARdata[nAlloc Lengt h]
TCHAR* data() // TCHAR* to manage d data
{ return (TCHAR*)(this+1); }
};
实际使用时,该结构体的所占用的内存块大小是不固定的,在CString内部的内存块头部,放置
的是该结构体。从该内存块头部开始的s izeof(Cstrin gData)个BYTE后才是真正的用于存放字符串
的内存空间。这种结构的数据结构的申请方法是这样实现的:
pData= (CStrin gData*) new BYTE[sizeof(CStrin gData) + (nLen+1)*sizeof(TCHAR)];
pData->nAlloc Lengt h = nLen;
其中nLen是用于说明需要一次性申请的内存空间的大小的。
从代码中可以很容易的看出,如果想申请一个256个TCHAR的内存块用于存放字符串,实际申请的大小是:
sizeof(CStrin gData)个BYTE+ (nLen+1)个TCHAR
其中前面sizeof(Cstrin gData)个BYTE是用来存放C strin gData信息的。后面的nLen+1个TCHA R 才是真正用来存放字符串的,多出来的一个用来存放’/0’。
CStrin g中所有的operations的都是针对这个缓冲区的。比如LPTSTR CStrin g::GetBuf fer(int nMinBu fLeng th),它的实现方法是:
首先通过CString::GetDat a()取得CStringDa ta对象的指针。该指针是通过存放字符串的指针m_pchDa ta先后偏移sizeof(Cstrin gData),从而得到了C Strin gData的地址。
然后根据参数nMinBufLen gth给定的值重新实例化一个C Strin gData对象,使得新的对象里的字符串缓冲长度能够满足nMinBuf Lengt h。
然后在重新设置一下新的CstringDat a中的一些描述值。C
最后将新CString Data对象里的字符串缓冲直接返回给调用者。
这些过程用C++代码描述就是:
if (GetDat a()->nRefs> 1 || nMinBu fLeng th > GetDat a()->nAlloc Lengt h)
{
// we have to grow the buffer
CStrin gData* pOldDa ta = GetDat a();
int nOldLe n = GetDat a()->nDataL ength; // AllocB uffer will trompit
if (nMinBu fLeng th < nOldLe n)
nMinBu fLeng th = nOldLe n;
AllocB uffer(nMinBu fLeng th);
memcpy(m_pchD ata, pOldDa ta->data(), (nOldLe n+1)*sizeof(TCHAR));
GetDat a()->nDataL ength = nOldLe n;
CStrin g::Releas e(pOldDa ta);
}
ASSERT(GetDat a()->nRefs<= 1);
// return a pointe r to the charac ter storag e for this string
ASSERT(m_pchD ata != NULL);
return m_pchD ata;
很多时候,我们经常的对大批量的字符串进行互相拷贝修改等,CStrin g 使用了Co
pyBefo reWri te技术。使用这种方法,当利用一个C Strin g对象a实例化另一个对象b的时候,其实两个对象的数值是完全相同的,但是如果简单的给两个对象都申请内存的话,对于只有几个、几十个字节的字符串还没有什么,如果是一个几K甚至几M的数据量来说,是一个很大的浪费。
因此CString 在这个时候只是简单的将新对象b的字符串地址m_pchData直接指向另一个对象a
的字符串地址m_pchData。所做的额外工作是将对象a的内存应用CStringDa ta:: nRefs加一。
CStrin g::CStrin g(constCStrin g& string Src)
{
m_pchD ata = string Src.m_pchD ata;
Interl ocked Incre ment(&GetDat a()->nRefs);
}
这样当修改对象a或对象b的字符串内容时,首先检查CString Data:: nRefs的值,如果大于一(等于一,
说明只有自己一个应用该内存空间),说明该对象引用了别的对象内存或者自己的内存被别人应用,该对象首先将该应用值减一,然后将该内存交给其他的对象管理,自己重新申请一块内存,并将原来内存的内容拷贝过来。
其实现的简单代码是:
void CStrin g::CopyBe foreW rite()
{
if (GetDat a()->nRefs> 1)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论