MFC中CString类的实现在strcore.cpp中,Cstring封装了一个用来存放字符串的缓冲区和对施加于这个字符串的操作。也就是说 ,CString里需要有一个用来存放字符串的缓冲区,并且有一个指针指向该缓冲区,该指针就是LPTSTR m_pchData。但是有些字符串操作会增建或减少字符串的长度,因此为了减少频繁的申请内存或者释放内存,Cstring会先申请一个大的内存块用来 存放字符串。这样,以后当字符串长度增长时,如果增加的总长度不超过预先申请的内存块的长度,就不用再申请内存。当增加后的字符串长度超过预先申请的内存 时,Cstring 先释放原先的内存,然后再重新申请一个更大的内存块。同样的,当字符串长度减少时,也不释放多出来的内存空间。而是等到积累到一定程度时,才一次性将多余 的内存释放。
还有,当使用一个Cstring对象a来初始化另一个Cstring对象b时,为了节省空间,新对象b并不分配空间,它所要做的只是将自己的指针 指向对象a的那块内存空间,只有当需要修改对象a或者b中的字符串时,才会为新对象b申请内存空间,这叫做写入复制技术(CopyBe foreWrite)。
这样,仅仅通过一个指针就不能完整的描述这块内存的具体情况,需要更多的信息来描述。
首先,需要有一个变量来描述当前内存块的总的大小。
其次,需要一个变量来描述当前内存块已经使用的情况。也就是当前字符串的长度
另外,还需要一个变量来描述该内存块被其他Cstring引用的情况。有一个对象引用该内存块,就将该数值加一。
这些过程用C++代码描述就是:
很多时候,我们经常的对大批量的字符串进行互相拷贝修改等,Cstring 使用了CopyBeforeWrite技术。使用这种方法,当利用一个Cstring对象a实例化另一个对象b的时候,其实两个对象的数值是完全相同的, 但是如果简单的给两个对象都申请内存的话,对于只有几个、几十个字节的字符串还没有什么,如果是一个几K甚至几M的数据量来说,是一个很大的浪费。因此 Cstring 在这个时候只是简单的将新对象b的字符串地址m_pchData直接指向另一个对象a的字符串地址m_pchData。所做的额外工作是将对象a的内存引 用CstringData:: nRefs加一
这样当修改对象a或对象b的字符串内容时,首先检查CStringData::nRefs的值,如果大于一(等于一,说明只有自己一个应用该内存空间), 说明该对象引用了别的对象内存或者自己的内存被别人应用,该对象首先将该引用值减一,然后将该内存交给其他的对象管理,自己重新申请一块内存,并将原来内 存的内容拷贝过来。
其中Release就是用来判断该内存的被引用情况的。
当多个对象共享同一块内存时,这块内存就属于多个对象,而不在属于原来的申请这块内存的那个对象了。但是,每个对象在其生命结束时,都首先将这块内存的引用减一,然后再判断这个引用值,如果小于等于零时,就将其释放,否则,将之交给另外的正在引用这块内存的对象控制。
Cstring使用这种数据结构,对于大数据量的字符串操作,可以节省很多频繁申请释放内存的时间,有助于提升系统性能。
CString使用中最典型的一个问题就是用来描述内存块属性的属性值和实际的值不一致。出现这个问题的原因就是CString为了方便某些应 用,提供了一些operations,这些operation可以直接返回内存块中的字符串的地址值,用户可以通过对这个地址值指向的地址进行修改,但 是,修改后又没有调用相应的operations1使CStringData中的值来保持一致。比如,用户可以首先通过operations得到字符串地 址,然后将一些新的字符增加到这个字符串中,使得字符串的长度增加,但是,由于是直接通过指针修改的,所以描述该字符串长度的CstringData中的 nDataLength却还是原来的长度,因此当通过GetLength获取字符串长度时,返回的必然是不正确的。
本文转自:http://tangfeng.javaeye.com/blog/491985