C++的类和动态内存分配(深拷贝与浅拷贝)并实现自己的string类

devtools/2024/10/22 10:59:55/

首先,我们先写一个并不完美的类:

#include<iostream>
#include<cstring>
using namespace std;class Mystring{private:char *p;int len;static int num;friend ostream& operator<<(ostream& os, const Mystring& c);public:Mystring(const char *q);Mystring(const Mystring& t);~Mystring();Mystring& operator=(const Mystring& t);
};
int Mystring::num=0;Mystring::Mystring(const char *q){len=strlen(q);p=new char[len+1];strcpy(p,q);num++;
}Mystring::~Mystring(){delete []p;p=nullptr;
}ostream& operator<<(ostream& os, const Mystring& c){os << c.p << "  num: " << c.len << "  all number:  " << c.num;return os;
}
int main(){Mystring a("asdf");Mystring b{a};Mystring c=b;Mystring d("ghjkl");d=c;cout << a << endl << b << endl << c << endl << d;
}

大家直接看代码,可能会有一点点费劲,这里讲解一下。我们这个类有一个char*p。这个地方很关键。因为我们在构造函数中,使用了new去动态创建了一个对象。这个对象中,拥有了这个动态创建的指针。

随后,我们使用了=号。用于一个对象为另一个对象赋值,拷贝。

问题随之而来,因为我们把指针也相等的赋值了过去。这意味着,两个对象的指针指向了同一段内存,而这一段内存是我们使用动态分配new主动分配的。当其中一个对象调用了析构函数,将自己销毁掉。这个对象里面指针所指向的内存也随之释放。但是,另一个对象的指针还指向了刚才被释放掉的内存。这个时候,如果继续对剩下的对象进行销毁,调用析构函数。就会对相同的内存连续释放(delete)了两次。从而导致程序崩溃。

C++提供下面这些默认函数(如果您没有提供):

  • 默认构造函数。不接受参数也不执行任何操作。
  • 默认析构函数,不执行任何操作。
  • 拷贝(复制)构造函数。用对象初始化另一个新建对象,逐个复制非静态成员,复制的是成员的值(浅复制)。
  • 拷贝赋值运算符。用对象赋值给另一个对象,逐个复制非静态成员,复制的是成员的值(浅复制)。
  • 移动构造函数。C++11增加。
  • 移动赋值运算符。C++11增加。
  • 地址运算符。返回对象的地址。和我们想象一致,不再讨论。

使用默认拷贝构造函数和使用默认=赋值运算符,导致浅拷贝,在析构时会出现重复释放(delete)同一段内存,导致程序崩溃。

怎么解决这个问题呢?

1、自己定义拷贝构造函数和重载=号,实现深拷贝。

如下,我们自己实现了重载=和自定义拷贝构造函数。(这也是为什么,一旦类中出现了指针,就需要自己定义=和拷贝构造函数,因为一定会出现浅拷贝)只有自己实现了这些功能,才会实现深拷贝。

#include<iostream>
#include<cstring>
using namespace std;class Mystring{private:char *p;int len;static int num;friend ostream& operator<<(ostream& os, const Mystring& c);public:Mystring(const char *q);Mystring(const Mystring& t);~Mystring();Mystring& operator=(const Mystring& t);
};
int Mystring::num=0;
Mystring::Mystring(const char *q){len=strlen(q);p=new char[len+1];strcpy(p,q);num++;
}
//重新写的拷贝构造函数
Mystring::Mystring(const Mystring& t){p=new char[t.len+1];len=t.len;strcpy(p,t.p);num++;
}
Mystring::~Mystring(){delete []p;p=nullptr;
}
//重新写的=号的重载
Mystring& Mystring::operator=(const Mystring& t){delete[]p;p=new char[t.len+1];strcpy(p,t.p);len=strlen(p);return *this;
}
ostream& operator<<(ostream& os, const Mystring& c){os << c.p << "  num: " << c.len << "  all number:  " << c.num;return os;
}
int main(){Mystring a("asdf");Mystring b{a};Mystring c=b;Mystring d("ghjkl");d=c;cout << a << endl << b << endl << c << endl << d;
}

浅拷贝:顾名思义,就是单纯的把值传了过去。但是因为涉及到了动态分配的内存,导致两个指针指向了同一个地方,最终释放的时候,会出现同一段内存被多次释放。最后崩溃掉。

而深拷贝:就是使用new重新再分配一段内存,分给拷贝的对象,不让两个指针指向同一段内存,只有他们各自指向了一段内存,才不会被多次释放。

重新写拷贝构造和重载=号,实际上就是多了一步new(分配内存)的步骤。代码如上,可以复制看看。

String s2(s1);String s3 = s1;string s4 = string(s1);string* ps4 = new string(s1);//1.调用拷贝构造函数//2.调用拷贝构造函数//3.调用拷贝构造函数//4.调用拷贝构造函数

如上,是四种拷贝函数的用法。都是调用了拷贝构造函数。区别在于,有的是临时对象,有的是直接构造,有的是动态分配了一个,然后构造。

这里说一个重点,如果有动态分配的内存,那么new和delete在构造和析构中,一定要一一对应。

如果,一个类中,出现了指针并且涉及到了动态内存分布,我们就必须要重新写  析构函数  拷贝构造函数  拷贝赋值 (移动拷贝构造) (移动拷贝赋值)

如果对象中出现指针成员变量,那么必须实现构造函数、析构函数、拷贝构造函数和=重载函数,以达到深拷贝。


每日金句:

                一切所谓不可能之事,其实都是尚未发生的事。

                                                                                                        -----------黄泉


http://www.ppmy.cn/devtools/125533.html

相关文章

前端面试题(十五)

83. ES6 中的 let 和 const let 和 const 的区别是什么&#xff1f; let 和 const 是 ES6 引入的用于声明变量的新方式&#xff0c;相比于传统的 var&#xff0c;它们具有以下特性&#xff1a; 块级作用域&#xff1a;let 和 const 声明的变量在其所在的块级作用域内有效&…

数据结构 ——— C语言实现带哨兵位双向循环链表

目录 前言 无哨兵位单向不循环链表的缺陷 带哨兵位双向循环链表的概念 带哨兵位双向循环链表的结构 带哨兵位双向循环链表逻辑结构示意图​编辑 实现带哨兵位双向循环链表的准备工作 实现带哨兵位双向循环链表 1. 创建新节点 2. 初始化哨兵位 3. 定义哨兵位指针 4. …

QD1-P23 CSS 基础选择器

本节学习&#xff1a;CSS 基础选择器&#xff08;5种&#xff09; 本节视频 https://www.bilibili.com/video/BV1n64y1U7oj?p23 基础选择器是 CSS 中最常用的选择器类型&#xff0c;它们用于选择 HTML 文档中的元素。以下是基础选择器的列表以及它们的优先级&#xff08;权重…

中国上市药品目录集数据库查询方法-支持数据下载

《中国上市药品目录集》由国家食品药品监督管理总局以数据库形式发布并实时更新&#xff0c;由CDE负责日常维护和管理。《中国上市药品目录集》收录了在中国批准上市的创新药、改良型新药、化学药品新注册分类的仿制药以及通过质量和疗效一致性评价的药品的具体信息。这个目录集…

XML 和 SimpleXML 简介

XML 和 SimpleXML 简介 XML(可扩展标记语言)是一种用于存储和传输数据的标记语言。它定义了一组规则,用于在文档中编码数据,以便人和机器都能理解。XML 的设计目标是既易于人类阅读,也易于机器解析。SimpleXML 是 PHP 中的一个扩展,用于处理 XML 数据。它提供了一个简单…

泡沫背后:人工智能的虚幻与现实

人工智能的盛世与泡沫 现今&#xff0c;人工智能热潮席卷科技行业&#xff0c;投资者、创业者和用户都被其光环吸引。然而&#xff0c;深入探讨这种现象&#xff0c;人工智能的泡沫正在形成&#xff0c;乃至具备崩溃的潜质。我们看到的&#xff0c;无非是一场由资本推动的狂欢…

【软件考试】一文学会原码,反码与补码

文章目录 三码互转十进制数转二进制 三码互转 在计算机中&#xff0c;数值通常以二进制形式表示&#xff0c;原码、反码和补码是三种不同的表示方法。 一、原码 概念&#xff1a; 原码是最直观的二进制表示法&#xff0c;最高位为符号位&#xff0c;0 表示正数&#xff0c;1 …

鸿蒙NEXT开发-动画(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…