IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】-- String深浅拷贝详解 -> 正文阅读

[C++知识库]【C++】-- String深浅拷贝详解

目录

一、浅拷贝和深拷贝定义

1.浅拷贝原理

2.深拷贝原理

二、浅拷贝和深拷贝实现

1.浅拷贝实现?

?2.深拷贝实现

(1)为什么引用类型成员使用浅拷贝不能实现拷贝构造?

(2)如何实现深拷贝?


一、浅拷贝和深拷贝定义

拷贝对象时,需要创建相同的字节序、类型、和资源。

1.浅拷贝原理

创建一个新对象, 来接收要重新复制或引用的对象值,要求该对象的所有成员变量全部都不在堆上分配空间。假如果对象的成员变量全部都是内置类型,复制的就是地址;如果对象的成员变量有引用数据类型,复制的就是内存中的地址。对其中一个对象的修改都会影响到另一个对象。

2.深拷贝原理

深拷贝将一个对象完整地从内存中拷贝出来给新对象,从堆中开辟新空间存放新对象。对新对象的修改不会改变原对象,实现两个对象的分离。

二、浅拷贝和深拷贝实现

1.浅拷贝实现?

当一个类对象的所有成员变量全部都是内置类型时,可以使用浅拷贝完成拷贝构造:

(1)显式定义拷贝构造函数完成浅拷贝;

(2)如果不显式定义拷贝构造函数,编译器会自动生成默认拷贝构造函数来完成浅拷贝。

如日期类的所有成员变量全部都是内置类型:

#include<iostream>
using namespace std;
 
class Date
{
public:
	//构造函数
	Date(int year = 2022, int month = 4, int day = 8)
	{
		_year = year;
		_month = month;
		_day = day;
	}
 
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
 
	//析构函数:清理资源
	~Date()
	{
		cout << "~Date()" << endl;//在析构函数内打印
	}
 
private:
	int _year;
	int _month;
	int _day;
};
 
int main()
{
	Date d1(2022, 9, 6);//调用构造函数
	Date d4(d1);
 
    d1.Print();
	d4.Print();
 
	return 0;
}

在没有显式定义拷贝构造函数的情况下,?d4构造成功了:

?2.深拷贝实现

(1)为什么引用类型成员使用浅拷贝不能实现拷贝构造?

对于引用类型的成员变量,如果在堆上开辟空间,不显式定义拷贝构造函数的话,会引发两个问题:

①调用析构函数时,这块空间被free了两次

②对其中一个对象进行修改,都会导致另外一个对象被修改

对于stack类,它的成员变量_a是在堆上开辟空间的,如果不显式定义拷贝构造函数,那么会引发程序崩溃:

#include <stdlib.h>
#include <iostream>
using namespace std;
 
typedef int STDataType;
 
class Stack
{
public:
	//构造函数
	Stack(int capacity = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * 4);
		_size = 0;
		_capacity = capacity;
	}
 
	//析构函数:清理资源
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}
 
private:
	STDataType* _a;
	int _size;
	int _capacity;
 
};
 
int main()
{
	Stack st1; 
	Stack st2(st1);
 
	return 0;
}

?这是因为调构造s1对象时,_a指向了堆上开辟的空间,由于没有显式定义拷贝构造函数,因此对象st2的成员变量_a拷贝的是st1的成员变量_a指针,即把st1的_a指针的值,拷贝给了st2的_a,那么两个指针的值是一样的,st1的_a和st2的_a指向同一块空间:

造成程序崩溃的原因:调用析构函数,这块空间被free了两次:后定义的先析构,st2先析构,free(_a)就把这块空间释放了,这块空间就被归还给了操作系统,再把_a置空了。再析构st1时,free(_a)还要释放这块空间,同一块空间被释放了两次。

另外,由于共用同一块空间,st1和st2无论谁被修改,都会导致对方也被修改。

(2)如何实现深拷贝?

①stack类使用深拷贝来拷贝构造对象:

#define  _CRT_SECURE_NO_WARNINGS  1
#include <stdlib.h>
#include <iostream>
using namespace std;

typedef int STDataType;

class Stack
{
public:
	//构造函数
	Stack(int capacity = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * 4);
		_size = 0;
		_capacity = capacity;
	}

	//拷贝构造函数
	Stack(const Stack& s)
		:_a(new STDataType[s._capacity])
		, _size(s._size)
		, _capacity(s._capacity)
	{
	}

	//析构函数:清理资源
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

private:
	STDataType* _a;
	int _size;
	int _capacity;

};

int main()
{
	Stack st1;
	Stack st2(st1);

	return 0;
}

st1和st2地址不一样,实现了深拷贝:?

②string类使用深拷贝来拷贝构造对象:

#define  _CRT_SECURE_NO_WARNINGS  1
#include<iostream>
using namespace std;

namespace delia
{
	class string
	{
	public:
        //构造函数
		string(const char* str = "")
		{
			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
		}
        
        //传统的拷贝构造函数
		string(const string& s)
			:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str, s._str);
		}

		char& operator[](size_t i)
		{
			return _str[i];
		}

		size_t size()
		{
			return strlen(_str);
		}

        const char* c_str()
        {
            return _str;
        }

        //析构函数
        ~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
	};
}

int main()
{
	delia::string s1("hello world");
	delia::string s2(s1);
	
	return 0;
}

F10-监视,可以看到s1._str和_s2._str的地址不同,各自拥有各自的空间,实现了深拷贝:

上面实现的是传统的拷贝构造,还有一种现代拷贝构造:

        //现代的拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(_str, tmp._str);
		}

监视发现,s1._str和s2._str地址不同,内容相同,实现了深拷贝:

现代拷贝构造做的事:

(1)将成员初始化成空指针

(2)用原对象成员构造临时对象

(3)交换临时对象和原对象成员

(4)出了拷贝构造函数会自动调用析构函数释放临时对象空间

?_str必须在初始化列表赋值成空指针的原因:构造tmp对象时使用s._str初始化,执行swap(_str, tmp._str);来交换this._str和tmp._str的内容,交换完毕后,tmp对象的成员内容为空指针,tmp出了拷贝构造函数作用域就会调用析构函数,会把tmp在堆上申请的空间释放掉,如果_str没有被赋值成空指针,那么_str就是随机值,交换后tmp对象的成员内容也为随机值,而随机值的空间是不能被释放的,会导致不可预知的错误,但是空指针是可以释放的,因此_str必须在初始化列表赋值成空指针。

还有现代版的赋值运算符重载:

        //赋值运算符重载
		string& operator=(string s)
		{
			swap(_str, s._str);
			return *this;
		}
int main()
{
	gxx::string s3("hello world");
	gxx::string s4;
	s4 = s3;

	return 0;
}

赋值运算符重载,把s3的成员值给了s ,那么s和s3有同样大小的空间和值,s4想要赋值成s,把s和s4进行交换,s的内容交换给了this,s的内容现在是s4原来的内容,s4原来的内容不要了,释放s即可,s的空间释放时,s作为局部对象,出了赋值运算符重载函数作用域就会调用析构函数释放s的空间,把原来s4的内容清理掉了:

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-13 11:37:19  更:2022-05-13 11:37:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 10:13:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码