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

news2024/11/23 6:48:18

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

#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.coloradmin.cn/o/2211538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT开发--多线程

第十四章 多线程 QThread 是 Qt 中实现多线程编程的核心类&#xff0c;提供跨平台线程管理。 使用 QThread 有两种方法&#xff1a; 1、 继承 QThread&#xff1a;重写 run() 方法&#xff0c;实现线程的具体操作。Qt4.8 之前较常用。 2、 使用 QObject 和 moveToThread()&…

2024-10-13 NO.1 Quest3 激活教程

文章目录 1 下载 Occlus 助手2 打开 quest 热点3 Quest3 连接 wifi4 详细文档5 参考教程 1 下载 Occlus 助手 ​ 网址&#xff1a;https://ochelper.xlemon.cn/home.html。 2 打开 quest 热点 ​ 下载 Occlus 助手后&#xff0c;双击 exe 文件打开。 ​ 过程中按照程序提示执…

各类排序详解

前言 本篇博客将为大家介绍各类排序算法&#xff0c;大家知道&#xff0c;在我们生活中&#xff0c;排序其实是一件很重要的事&#xff0c;我们在网上购物&#xff0c;需要根据不同的需求进行排序&#xff0c;异或是我们在高考完报志愿时&#xff0c;需要看看院校的排名&#…

【动手学深度学习】6.2 图像卷积(个人向笔记)

1. 互相关运算 严格来说&#xff0c;卷积层是一个错误的叫法&#xff0c;因为它本质上是互相关运算而不是卷积运算。我们暂时忽略通道看看二维图像数据和隐藏表示。那么输出大小可以表示为 我们自己实现一个二维互相关运算 2. 卷积层 卷积层中有两个参数&#xff1a;卷积核权…

Medieval Kingdom UI 中世纪王国AAA级UI游戏界面

这款中世纪王国风格的大型素材包包含大量绘图、图标、用户界面(UI)元素、完整的世界地图和文明图标。它将助您打造一款游戏,或为您的3D游戏增添亮点。您还可以为对话制作国王的动画,为4K游戏创建独特的面板和窗口。提供两种独特皮肤:经典(冷色调)和白金(暖色调)。 素…

国家基本药物目录数据库查询3种方法(2018、2012、2009年版)

国家基本药物目录是一份由国家卫生健康委员会等相关部门制定的药品清单&#xff0c;旨在满足国家公共卫生需求&#xff0c;保障基本医疗服务。该目录包括了多种药品&#xff0c;覆盖了不同的疾病治疗领域&#xff0c;如抗生素、心血管药物、神经系统药物、抗肿瘤药物、维生素和…

STM32 -- USB CDC 虚拟串口通信

本篇操作: 通过CubeMX Keil&#xff0c;配置STM32作为USB设备端&#xff0c;与电脑上位机进行通信&#xff08;CDC&#xff09;&#xff1b;通用带USB功能的 STM32 芯片 &#xff08;如F1、F4等&#xff0c;系统时钟配置不同&#xff0c;代码通用&#xff09;。 目录 一、 S…

[论文精读]Active and Semi-Supervised Graph Neural Networks for Graph Classification

论文网址&#xff1a;Active and Semi-Supervised Graph Neural Networks for Graph Classification | IEEE Journals & Magazine | IEEE Xplore英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若…

大数据-168 Elasticsearch 单机云服务器部署运行 详细流程

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

PyQt: QMessageBox Duplication

在使用 PyQt 的 QMessageBox 时&#xff0c;如果你遇到 消息框重复显示 或 QMessageBox 重复实例化 的问题&#xff0c;通常是因为消息框没有正确管理或关闭&#xff0c;或者消息框的创建和显示逻辑中存在重复调用。以下是一些常见原因和解决方案。 1、问题背景 在 PyQt 中使用…

无心剑七绝《泊院雕楼》

七绝泊院雕楼 清歌咏尽桂花香 泊院雕楼醉夕阳 逸兴无端飞万里 幽情宛转忆潇湘 2024年10月13日 平水韵七阳平韵 这首七绝《泊院雕楼》以清新脱俗的语言&#xff0c;描绘了一幅宁静致远的画面。 首句“清歌咏尽桂花香”&#xff0c;以“清歌”起兴&#xff0c;形象地描绘了桂花香…

C++——类和对象(三)

一.赋值运算符 1.运算符重载 (1) 运算符重载是具有特殊名字的函数&#xff0c;他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样&#xff0c;它也具有其返回类型和参数列表以及函数体。 (2) 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。…

React.createRef(),React.forwardRef(),forwardRef()结合next.js的link进行路由跳转

码云https://gitee.com/skyvilm/react-next.js 1.React.createRef() 作用&#xff1a;获取dom元素 使用 import React,{Component} from react export default class Index extends Componen{ constructor(props){ super(props) this.myrefReact.createRef(); //创建节点 } c…

如何批量从sql语句中提取表名

简介 使用的卢易表 的提取表名功能&#xff0c;可以从sql语句中批量提取表名。采用纯文本sql语法分析&#xff0c;无需连接数据库&#xff0c;支持从含非sql语句的文件文件中提取&#xff0c;支持各类数据库sql语法。 特点 快&#xff1a;从成百个文件中提取上千个表名只需1…

集成方案 | 借助 Microsoft Copilot for Sales 与 Docusign,加速销售流程!

加速协议信息提取&#xff0c;随时优化邮件内容~ 在当今信息爆炸的时代&#xff0c;销售人员掌握着丰富的数据资源。他们能够通过 CRM 平台、电子邮件、合同库以及其他多种记录系统&#xff0c;随时检索特定个人或组织的关键信息。这些数据对于销售沟通至关重要。然而&#x…

【端到端】CVPR 2023最佳论文:UniAD解读

作者&#xff1a;知乎一根呆毛授权发布 传统的端到端网络是用多个小model串起来&#xff0c;但这会有误差累积的问题&#xff0c;因此我们提出了UniAD&#xff0c;一个综合框架&#xff0c;把所有任务整合到一个网络。整一个网络都是为planner而进行设计的。 Introduction a传…

SQL性能优化指南:如何优化MySQL多表join场景

目录 多表join问题SQL 这里解释下 Using join buffer (Block Nested Loop)&#xff1a; 对性能产生的影响&#xff1a; 三种join算法介绍 join操作主要使用以下几种算法&#xff1a; &#xff08;1&#xff09;Nested Loop Join &#xff08;2&#xff09;Block Nested …

生信服务器配置:优化生物信息学数据处理的最佳实践

介绍 在生物信息学研究中&#xff0c;处理和分析大规模数据集&#xff08;如基因组、转录组和蛋白质组数据&#xff09;需要强大的计算资源和精确的服务器配置。生信服务器配置的优化可以显著提高数据处理的效率和结果的准确性。本文将探讨生信服务器配置的关键要素&#xff0…

【LeetCode热题100】分治-快排

本篇博客记录分治快排的4道题目&#xff1a;颜色分类、排序数组、数组中的第K个最大元素、数组中最小的N个元素&#xff08;库存管理&#xff09;。 class Solution { public:void sortColors(vector<int>& nums) {int n nums.size();int left -1,right n;for(int…

【实战项目】——Boost搜索引擎(五万字)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、项目的相关背景 1.1、什么是Boost库&#xff1f; 1.2、什么是搜索引擎&#xff1f; 1.3、为什么要做Boost库搜索引擎&#xff1f; 二、搜索引擎的宏观原…