STL之浅拷贝&深拷贝&写实拷贝

目录

一、采用引用计数器类解决浅赋值的问题

1、引用计数器类中

2、string类中的四个函数

二、写实拷贝

三、代码实现


我们先来思考一个问题:

浅拷贝和深拷贝到底哪个好呢?

浅拷贝:

在string类中,对象成员的类型是指针,这时候要是就行浅拷贝的话,就相当于是进行了指针的赋值,两个指针指向了同一块空间,

这样的话在析构函数调用的时候,对同一个空间会进行释放两次,就会引起double free,这是浅拷贝的一个很大的缺陷

深拷贝:

深拷贝是不会让两个不同的指针指向同一块空间的,他在每次拷贝的时候都会去重新开辟一个新的空间出来,那么也就不会发生

double free的问题,但有一个问题就是浪费空间,例如以下这个例子:

        一个宿舍要买报纸:

   1、如果只是读的话,那一个宿舍只用买一份报纸就行了,一个宿舍可以公用同一份        这时候浅拷贝好

    2、但如果要在报纸上写东西或者裁剪的话,就必须每个人买一份报纸了               这时候就必须采用深拷贝     

正如一个宿舍买报纸只买一份报纸,如果多个指针指向同一个空间,可以共用同一份数据的话那该多好啊

假如浅拷贝能够解决double  free问题的话,浅拷贝就很nice了

那么有没有方法解决呢?

如果可以统计指向一块空间的对象的数量的话,那么就能够做到当没有对象指向空间的时候,再将空间释放掉

下面介绍一种方法:

一、采用引用计数器类解决浅赋值的问题

1、引用计数器类中

我们可以创建一个引用String_rep,用来辅助string

这个类的底层私有数据有两个:

char* m_data;     //用来指向字符串
int m_count;       //用来进行引用计数 ,统计指向这个类的指针的个数

STL之浅拷贝&深拷贝&写实拷贝_第1张图片这个类的结构如图

 那么String类的底层私有数据只需要一个  String_rep*  rep,一个指向引用计数类的指针

引用计数类中的构造,析构,赋值,拷贝构造就正常写就行了,就是对m_data指针空间的开辟和释放就行

同时这个类里面需要包含两个函数:

1、void Increment(),用来对m_count进行+1

2、void Decrement(),用来对m_count进行-1,同时当m_count==0(没有指针指向)的时候要注意

这里对  void Decrement()进行剖析一下

STL之浅拷贝&深拷贝&写实拷贝_第2张图片

 当--m_count等于0的时候,代表要对空间进行释放,这里采用的是delete  this,

要知道,this在这里其实是个对象类型的(即String类中的rep指针 )这个操作相当于:

STL之浅拷贝&深拷贝&写实拷贝_第3张图片

 这个操作就很妙                      一次性解决两个空间的问题     数据空间的释放和  自身对象的释放 

2、string类中的四个函数

数据成员就一个    String_rep*  rep;

这里面的构造,赋值,拷贝构造,析构不大一样

我们可以都来看下:

构造:

STL之浅拷贝&深拷贝&写实拷贝_第4张图片

 拷贝构造:

STL之浅拷贝&深拷贝&写实拷贝_第5张图片

 这里的拷贝构造用到的是浅拷贝:相当于直接指针进行值传递了,就是让两个对象指向同一个计数器类

同时调用Increment()

这里的实现相当于:

STL之浅拷贝&深拷贝&写实拷贝_第6张图片

 赋值

STL之浅拷贝&深拷贝&写实拷贝_第7张图片

这里也是浅赋值,原理也是比较简单的

 STL之浅拷贝&深拷贝&写实拷贝_第8张图片

 析构

STL之浅拷贝&深拷贝&写实拷贝_第9张图片

 四个函数写完以后,浅拷贝的基本问题就都解决了,接下来就要慢慢引出写实拷贝

二、写实拷贝

在Linux中,创建子进程的过程是写实拷贝的,那么在c++中的写实拷贝又是什么呢?

我们先来看下这个问题:

我们想写一个toupper函数,将小写字母转换为大写字母

STL之浅拷贝&深拷贝&写实拷贝_第10张图片

 这里我们写出了方法。

我们来测试一下:

STL之浅拷贝&深拷贝&写实拷贝_第11张图片

 这里我们只是s1调用了这个函数,但s2的值也发生了改变,这不是我们想要得到的。

为什么会这样呢?

因为s1和s2的rep指向的空间是一样的,是浅赋值创建的,对一个进行值的操作,那么所以的都会改变

为了解决这个问题,那么就需要进行写实拷贝

和之前Linux的方法一样,

STL之浅拷贝&深拷贝&写实拷贝_第12张图片

这个函数进行的操作如下:

STL之浅拷贝&深拷贝&写实拷贝_第13张图片

 三、代码实现

#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;

}
*/

你可能感兴趣的:(C++,c++,开发语言)