在 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;
};