当前位置: 首页 > news >正文

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

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

#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.mrgr.cn/news/49441.html

相关文章:

  • vue3之 shallowRef、markRaw
  • [Linux] 软硬链接
  • 7.2-I2C的DMA中断
  • RAII - 安卓中的智能指针
  • 如何有效参与机器人顶会?——周易教授PRE-IROS 2024分享
  • GS-LRM: Large Reconstruction Model for 3D Gaussian Splatting 论文解读
  • 性能测试-JMeter(3)
  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
  • CountUp.js 实现数字增长动画 Vue
  • 苏姿丰发布AMD最强AI芯片|苹果质疑大语言模型根本无法进行逻辑推理|Kimi的商业化困局|杰弗里·辛顿:“图灵诺奖双得主”、“AI教父”,至高荣誉加身
  • RabbitMQ 高级特性——死信队列
  • MySQL从入门到跑路
  • AI/LLM 大模型入门指南相关的命令行脚本
  • OpenCV系列教程四:特征检测与特征匹配,图像查找、对齐和拼接
  • Yocto - 使用Yocto开发嵌入式Linux系统_09 深入了解BitBake元数据
  • 用SAM2和Cutie模型目标追踪
  • 程序地址空间 -- 详解
  • Download Vmware Fusion (free for person)
  • Dev-Cpp 5.11 安装教程【保姆级】
  • vue3之生命周期钩子