在 string类的模拟之深浅拷贝 中,我们可以看到,程序浅拷贝导致的崩溃,是因为多次释放内存引起的多次析构,那么我们可以添加一个计数器,这里称之为引用计数,来表示当前的空间有多少指针指向它,如果大于一的话,那么在调析构函数的时候,就只对引用计数减一,当减到只剩一的时候就可以安心的释放内存空间了。
引用计数的本质就是一个整形变量,那么我们到底应该怎么给出引用计数的形式呢?
1.整形成员变量(不可以,每个对象都会单独存一份,就会导致计数器并不统一(数值只在相邻的对象发生传递),在析构的时候内存无法释放,导致内存泄漏);
2.静态成员变量 (不可以,静态成员变量为所有对象共享,任何对象都可以对它进行修改,每创建一个对象我们都对计数器加1,却忽略了创建的新对象是否与已存在的对象占同一块空间)
3.指针变量(可以)
class String { public: String(const char* pStr = "")//构造函数 :_pCount(new int(0)) , _pStr(new char[strlen(pStr) + 1]) { strcpy(_pStr, pStr); *_pCount = 1; } String(const String& s) :_pCount(s._pCount) { _pStr = (char*)s._pStr; _pCount = s._pCount; (*_pCount)++; } ~String()//析构 { if (NULL != _pStr) { if (--(*_pCount) == 0) { delete[]_pCount;//释放计数器指针 !!! delete[]_pStr; _pStr = NULL; _pCount = NULL; } } } String& operator=(String& s)//赋值运算符重载 { if (_pStr != s._pStr) { _pStr = s._pStr; _pCount = s._pCount; (*_pCount)++; } return *this; } private: int* _pCount; char* _pStr; }; void Funtest() { String s1("abcd"); String s2(s1); String s3 = "1111";//调用拷贝构造函数(编译器会s2直接初始化s3) String s4;//s4对象已经存在了 s4 = s3;//编译器会调用赋值运算符重载将s3的值赋给s4 }
至此,好像已经解决了多次析构的问题,但是还不够好(开辟了两块较小的内存空间,造成内存的碎片化问题)。我们采取下面这样的方法来避免。
再说说每次拷贝都开辟内存空间,这样的做法虽然有效的避免了内存的多次释放的问题,但它带来的问题确是,需要不停的开辟内存,尽管可能只是读一下内存中的字符而已,这就造成的系统资源的浪费。由此,我们可以采用写时拷贝的方式来避免这样的问题。
只要S2的操作只是读取内存时,那么并不会开辟空间,而发生写(增删改)的操作时,我们就新开辟空间。改变S2的指向。
String.cpp
#define _CRT_SECURE_NO_WARNINGS 1 #include "String.h" int& String::GetRefCount() { return *((int *)(_str - 4)); } String::~String() { if (--GetRefCount() == 0) { delete[](_str - 4); } } String::String(const String& s) :_str(s._str) { GetRefCount()++; } String& String::operator=(const String& s) { if (_str != s._str) //自赋值 { if (--GetRefCount() == 0) //只有一个引用的时候 { delete[](_str - 4); } _str = s._str; //指向位置改变,引用计数增加 GetRefCount()++; } return *this; } const char* String::c_str() { return _str; } void String::CopyOnWrte() { if (GetRefCount() > 1) //引用计数大于一 ? 在使用的函数中判断减少压栈过程 { GetRefCount()--; char* newstr = new char[strlen(_str) + 5]; strcpy(newstr + 4, _str); _str = newstr + 4; GetRefCount() = 1; } } char& String::operator[](size_t pos) { CopyOnWrte(); return _str[pos]; } void TestClass() { String s1("hello world!"); String s2(s1); String s3("this is czf"); s1 = s3; String s4(s3); s3[3] = 'r'; } int main() { TestClass(); return 0; }String.h
#pragma once
#include <string.h>
#define NULL 0
class String
{
public:
String(char * str = "")
:_str(new char[strlen(str) + 5])
{
_str += 4;
strcpy(_str, str);
*((int *)(_str - 4)) = 1;
}
String(const String& s);
String& operator=(const String& s);
~String();
const char *c_str();
void CopyOnWrte();
int& GetRefCount();
char& operator[](size_t pos);
private:
char *_str;
};