16,548
社区成员




struct CStringData
{
long lRefs; // 引用记数
long lDataLength; // 字符使用长度
long lMallocLength; // 分配长度
// 数据区,存放字符串的地方
TCHAR* data()
{
return (TCHAR*)(this+1);
}
};
2、CString类
class MyCString
{
public:
MyCString(/*LPCTSTR lpStr=NULL*/);
~MyCString(void);
void Initialize();
private:
LPTSTR m_lpszData; //指向CStringData的数据区
};
3、全局数据区:
long g_InitData[] = { -1, 0, 0, 0 };
CStringData* g_Data = (CStringData*)g_InitData; //全局CStringData指针,所有初始化为空的MyCString对象的CStringData指针都指向这里
LPCTSTR g_pCStr = (LPCTSTR)(((BYTE*)g_InitData)+sizeof(CStringData)); //相当于LPCTSTR g_pCStr = (LPCTSTR)g_Data->data(),值为空
//生成一个空的CString
const MyCString& GetEmptyString()
{
return *(MyCString*)&g_pCStr;
}
4、默认构造函数:
MyCString::MyCString(void)
{
Initialize();
}
void MyCString::Initialize()
{
m_lpszData = g_pCStr;
}
然而现实中我看过很多人的代码都这样初始化一个空的CString: CSting str = _T(""); 实在是令人汗颜!
// 声明4个CString
CString s1, s2, s3, s4;
s4.Format( L"0x%X 0x%X 0x%X\n", (PCTSTR)s1, (PCTSTR)s2, (PCTSTR)s3 );
TRACE( s4 ); // 显示0xF5B8F8 0xF5B8F8 0xF5B8F8,三个一样的地址,内容是0
// GetBuffer();
s1.GetBuffer();
s2.GetBuffer();
s3.GetBuffer();
s4.Format( L"0x%X 0x%X 0x%X\n", (PCTSTR)s1, (PCTSTR)s2, (PCTSTR)s3 );
TRACE( s4 ); // 显示0x12CD830 0x12CD9A0 0x12CD9F0,重分配内存,地址不一样,长度是1,内容是0
// 赋值
s1 = "a";
s2 = "bb";
s3 = "ccc";
s4.Format( L"0x%X 0x%X 0x%X\n", (PCTSTR)s1, (PCTSTR)s2, (PCTSTR)s3 );
TRACE( s4 ); // 0x12CD830 0x12CD7A8 0x12CDAE0,s1在原来的基础上扩大,s2和s3重分配别的内存,新分配的内存比字符串长,便于以后再加内容。
// ReleaseBuffer
s1.ReleaseBuffer();
s2.ReleaseBuffer();
s3.ReleaseBuffer();
s4.Format( L"0x%X 0x%X 0x%X\n", (PCTSTR)s1, (PCTSTR)s2, (PCTSTR)s3 );
TRACE( s4 ); // 0x12CD830 0x12CD7A8 0x12CDAE0,地址不变,释放多分配的内存
测试环境VC2010
其实,一般情况下不用ReleaseBuffer,除非长时间占用了很大的空余空间。
值得一提的是,如果一个CString 反复赋不同长度的值,应该先用GetBuffer申请一个较大的空间,中间千万不能用ReleaseBuffer,否则会反复销毁和分配内存,降低效率,增加内存碎片。CString str;
::GetModuleFileName(NULL, LPCTSTR(str), str.GetLength() + 1);
LPCTSTR(str) 指向的空间是多大呢?CString 如果没有初始化字符串,那么他内部的指针就是指向一个 TCHAR(1 或 2 字节)且内容为零,表示一个空字符串。所以这样的一个调用是根本无法成功的。TCHAR szPath[MAX_PATH];
::GetModuleFileName(NULL, szPath, _countof(szPath));
这样的缓冲区大小是确定了,260 个 TCHAR。调用成功后,其中也只是保存了本程序的完整路径。如果本程序的路径为 C:\test.exe,那么其中只有 12 个字符是有用的,其他的控件就浪费掉了。CString str;
::GetModuleFileName(NULL, str.GetBuffer(MAX_PATH), MAX_PATH);
这样就让 CString 内部新分配 MAX_PATH 个 TCHAR 大小返回这段内存的指针,可以成功调用。当函数调用成功后,和第二种情况一样,假如文件名只有 12 个字符有用,剩下的 260 - 12 个字符就有些浪费了。CString 类的意义在于他能根据字符串的长度来分配内存,因此才有 ReleaseBuffer。CString str;
::GetModuleFileName(NULL, str.GetBuffer(MAX_PATH), MAX_PATH);
str.ReleaseBuffer();
ReleaseBuffer 实际上就是完成将字符串指向的空间重新按照 CString 类存储数据的方式分配够用的新空间来储存字符串,并非必需。而 MFC 中那些以 CString 作为参数的成员函数,其内部也是把如上的步骤重新实现了一次而已。下面是 CWnd::GetWindowText 的源码(CWnd::GetWindowText 有两个版本,这是其中一个):void CWnd::GetWindowText(CString &rString) const
{
int nLen = ::GetWindowTextLength(m_hWnd);
::GetWindowText(m_hWnd, rString.GetBufferSetLength(nLen), nLen+1);
rString.ReleaseBuffer();
}
可见其内部也是帮你实现 GetBuffer/GetBufferSetLength 而已。如果你用另外一个版本,那么自然就需要手动分配空间。