目录
一、采用引用计数器类解决浅赋值的问题
1、引用计数器类中
2、string类中的四个函数
二、写实拷贝
三、代码实现
我们先来思考一个问题:
浅拷贝和深拷贝到底哪个好呢?
浅拷贝:
在string类中,对象成员的类型是指针,这时候要是就行浅拷贝的话,就相当于是进行了指针的赋值,两个指针指向了同一块空间,
这样的话在析构函数调用的时候,对同一个空间会进行释放两次,就会引起double free,这是浅拷贝的一个很大的缺陷
深拷贝:
深拷贝是不会让两个不同的指针指向同一块空间的,他在每次拷贝的时候都会去重新开辟一个新的空间出来,那么也就不会发生
double free的问题,但有一个问题就是浪费空间,例如以下这个例子:
一个宿舍要买报纸:
1、如果只是读的话,那一个宿舍只用买一份报纸就行了,一个宿舍可以公用同一份 这时候浅拷贝好
2、但如果要在报纸上写东西或者裁剪的话,就必须每个人买一份报纸了 这时候就必须采用深拷贝
正如一个宿舍买报纸只买一份报纸,如果多个指针指向同一个空间,可以共用同一份数据的话那该多好啊
假如浅拷贝能够解决double free问题的话,浅拷贝就很nice了
那么有没有方法解决呢?
如果可以统计指向一块空间的对象的数量的话,那么就能够做到当没有对象指向空间的时候,再将空间释放掉
下面介绍一种方法:
我们可以创建一个引用String_rep,用来辅助string 类
这个类的底层私有数据有两个:
char* m_data; //用来指向字符串
int m_count; //用来进行引用计数 ,统计指向这个类的指针的个数
那么String类的底层私有数据只需要一个 String_rep* rep,一个指向引用计数类的指针
引用计数类中的构造,析构,赋值,拷贝构造就正常写就行了,就是对m_data指针空间的开辟和释放就行
同时这个类里面需要包含两个函数:
1、void Increment(),用来对m_count进行+1
2、void Decrement(),用来对m_count进行-1,同时当m_count==0(没有指针指向)的时候要注意
这里对 void Decrement()进行剖析一下
当--m_count等于0的时候,代表要对空间进行释放,这里采用的是delete this,
要知道,this在这里其实是个对象类型的(即String类中的rep指针 )这个操作相当于:
这个操作就很妙 一次性解决两个空间的问题 数据空间的释放和 自身对象的释放
数据成员就一个 String_rep* rep;
这里面的构造,赋值,拷贝构造,析构不大一样
我们可以都来看下:
构造:
拷贝构造:
这里的拷贝构造用到的是浅拷贝:相当于直接指针进行值传递了,就是让两个对象指向同一个计数器类
同时调用Increment()
这里的实现相当于:
赋值
这里也是浅赋值,原理也是比较简单的
析构
四个函数写完以后,浅拷贝的基本问题就都解决了,接下来就要慢慢引出写实拷贝了
在Linux中,创建子进程的过程是写实拷贝的,那么在c++中的写实拷贝又是什么呢?
我们先来看下这个问题:
我们想写一个toupper函数,将小写字母转换为大写字母
这里我们写出了方法。
我们来测试一下:
这里我们只是s1调用了这个函数,但s2的值也发生了改变,这不是我们想要得到的。
为什么会这样呢?
因为s1和s2的rep指向的空间是一样的,是浅赋值创建的,对一个进行值的操作,那么所以的都会改变
为了解决这个问题,那么就需要进行写实拷贝
和之前Linux的方法一样,
这个函数进行的操作如下:
#define _CRT_SECURE_NO_WARNINGS
#include
#include"字符串的深拷贝.h"
using namespace std;
class String;
//引用计数器类
class String_rep
{
friend class String;
friend ostream& operator<<(ostream& out, const String& s);
public:
String_rep(const char* str = ""):m_count(0)
{
m_data = new char[strlen(str) + 1];
strcpy(m_data, str);
}
String_rep(const String_rep& rep):m_data(nullptr),m_count(0)
{
String_rep _Tmp(rep.m_data);
swap(m_data, _Tmp.m_data);
}
String_rep& operator=(const String_rep& rep)
{
if (this != &rep)
{
String_rep _Tmp(rep);
swap(m_data, _Tmp.m_data);
}
return *this;
}
~String_rep()
{
delete[]m_data;
m_data = nullptr;
}
public:
void Increment()
{
m_count++;
}
void Decrement()
{
if (--m_count == 0)
{
delete this; //自杀行为, 妙哉!
//一次性解决两个空间的问题
//数据空间的释放和自身对象的释放
}
}
private:
char* m_data;
int m_count;//引用计数
};
class String
{
friend ostream& operator<<(ostream& out, const String& s);
public:
String(const char* str = ""):rep(new String_rep(str))
{
rep->Increment();
}
String(const String& s):rep(s.rep)//浅拷贝,让两个指针指向的内容一样
{
rep->Increment();
}
String& operator=(const String& s)
{
if (this != &s)
{
rep->Decrement();//这里也是浅赋值
rep = s.rep;
rep->Increment();
}
return *this;
}
~String()
{
rep->Decrement();
}
public:
//这时候有问题,一个对象改变是不应该去影响另一个对象的
//这时候写实拷贝就要了
void to_upper()
{
//通过自身的引用记术器对象先去拷贝构造一个新的引用计数器对象
//谁更改数据,谁就要进行一次写实拷贝,这和Linux子进程创建一模一样。。牛的
String_rep *new_rep=new String_rep(rep->m_data);
rep->Decrement();
rep=new_rep;
rep->Increment();
//先完全拷贝原来的数据,当要进行修改的时候再把数据真正的进行修改
char* pch = rep->m_data;
while (*pch != '\0')
{
if (*pch >= 'a' && *pch <= 'z')
{
*pch -= 32;
}
pch++;
}
}
private:
String_rep* rep;
};
ostream& operator<<(ostream& out, const String& s)
{
out << s.rep->m_data;
return out;
}
void main()
{
String s1("abc");
String s2 = s1;
cout << "s1= " << s1 << endl;
cout << "s2= " << s2 << endl;
s1.to_upper();
cout << "s1= " << s1 << endl;
cout << "s2= " << s2 << endl;
}
/*
void main()
{
String s("abc");
String s1=s;
String s2("xyz");//和s、s1有不同的引用计数器体系
s2 = s1;
}
*/