C++面向对象编程(OOP)-浅拷贝与深拷贝

news2024/9/25 15:29:24

本文主要使用详细的代码比较说明深拷贝和浅拷贝的区别,延伸讲到构造函数。并提供深拷贝和浅拷贝的对比代码。

目录

1 深拷贝和浅拷贝引入原因

2 深拷贝个浅拷贝基本介绍

3 浅拷贝的弊端

4 拷贝构造函数

5 类中有指针的浅拷贝

6 类中有指针的深拷贝


1 深拷贝和浅拷贝引入原因

        拷贝简单理解就是赋值操作,就是将一个对象的数据拷贝给另一个同类型的数据。由于常规的拷贝直接使用赋值运算符实现,这是系统会默认生成一个默认的拷贝构造函数,这个拷贝构造函数是浅拷贝的。不会对对象中动态申请的空间中的内容进行拷贝,仅仅只是对申请空间首地址的拷贝,因此会引发安全问题,这也是浅拷贝和深拷贝引入的原因。

2 深拷贝个浅拷贝基本介绍

        浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是在对象复制过程中的两种不同方式:

        1  在未定义拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝(不用自己构造),它能够完成成员的简单的值的拷贝一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址(同一个堆区),当对象快结束时,会调用两次析构函数(析构函数也无需自己构造,但想要知道析构函数的工作可以自己构造析构函数用输出来记录),而导致指针悬挂现象,所以,此时,必须采用深拷贝。
        2 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据(新的堆区空间进行拷贝),从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

3 浅拷贝的弊端

        浅拷贝会使得空间进行重复释放,引起错误,这种错误一般会出现在类中有指针成员;

        深拷贝需要单独构造拷贝构造函数,即对于指针成员需要在对象的实例化时,动态申请内存空间,而且不会引起内存的重复释放。

4 拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,它在创建新对象时,会使用同一类中已存在的对象来初始化新创建的对象。拷贝构造函数在以下情况下会被调用:通过使用另一个同类型的对象来初始化新创建的对象;复制对象把它作为参数传递给函数;以及复制对象,并从函数返回。

对于基本数据类型,例如int,赋值操作如int a=4; int b=a;就涉及到了拷贝。而对于复杂的类对象来说,由于其内部结构可能相当复杂,包含多种成员变量,因此拷贝过程就会相对复杂一些。

一个简单的例子:

#include<iostream>
using namespace std;

class Complex {
public:
    double real, imag;
    Complex(double r, double i) {
        real = r;
        imag = i;
    }
};

int main(){
    Complex cl(1, 2); // 创建一个复数对象cl,其实部为1,虚部为2
    Complex c2 ( cl ); // 用复制构造函数初始化c2,使得c2的实部和虚部与cl相同
    cout << c2.real <<","<< c2.imag << endl; // 输出结果应为"1,2"
    return 0;
}

    在这个例子中,我们定义了一个名为Complex的类,它有两个成员变量:realimag。然后我们创建了一个Complex类型的对象cl,并用它来初始化另一个对象c2。在这个过程中,我们就用到了拷贝构造函数。

        注意:对于浅拷贝来讲,系统默认的构造函数即可满足,且形如:Complex c2(cl); 的拷贝构造函数,与Complex c2 = cl;的结果是类似的,后者是运算符实现的。

5 类中有指针的浅拷贝

        首先演示一段错误的代码:(这是一个有指针成员的类的实例)

#include <iostream>
#include <cstring>
#include <string>
using namespace std;

namespace DAY11_SHALLOW_CPY
{
    class Employee {
        public:
            char* name;
            int age;
            char* position;
            string objName;
        public:
            Employee(const char* n, int a, const char* p,const string str) : name(new char[strlen(n) + 1]), age(a), position(new char[strlen(p) + 1]) ,objName(str){
                strcpy(name, n);
                strcpy(position, p);
                cout << "构造函数 初始化参数" << endl;
            }

            // 浅拷贝构造函数
            Employee(const Employee& other) : name(other.name), age(other.age), position(other.position) ,objName(other.objName) {cout << "构造函数 浅拷贝" << endl;} 
        
            ~Employee() {
                
                delete[] this->name;
                delete[] this->position;
                cout << this->objName << "析构函数" << endl;
                
                
            }
        
            void print() const 
            {
                cout << "name: " << this->name << " age:" << this->age << " position: " << this->position << endl;
            }
    };
    
};


int main(int argc, char *argv[])
{

    cout << "\n \n \n";
    cout << "***************************************************浅拷贝解析***************************************************" << endl;
    cout << "\n \n \n";
    // 浅拷贝
    {
        using namespace DAY11_SHALLOW_CPY;
        char hu1[] = "Tom";
        char hu2[] = "ShangHai";
        int ku1 = 23;

        char hu11[] = "Hubery";
        char hu22[] = "BeiJing";
        int ku11 = 28;

        cout << "*******step1*******" << endl;
        Employee t1(hu1,ku1,hu2,"t1");
        cout << "*******step2*******" << endl;

        Employee t2 = t1;

        cout << "*******step3*******" << endl;

        strcpy(t2.name,hu11);
        strcpy(t2.position,hu22);
        t2.objName = "t2";
        t2.age = ku11;

        cout << "t1:********* " << endl;
        t1.print();

        cout << "t2:********* " << endl;
        t2.print();
        cout << "*******step4*******" << endl;
        
    }

    return 0;
}

        运行结果:

a0179c14dfe842f291e4e1d26e3d8fd0.png

        根据结果可以看到,对实例t2中指针指向的空间的修改,改变了t1中相应的值,这就是浅拷贝,在拷贝时,仅仅只是拷贝了指针,而没有拷贝一份完整的数据出来,因此当一个实例修改数据时影响了另一个实例的数据,且在释放时,由于RALL机制,析构时释放,会出现重复释放。运行结果中“free(): double free detected in tcache 2” 就是这个意思。

        解决这个问题的办法就是进行深拷贝。

6 类中有指针的深拷贝

        解决5中重复释放的问题,代码如下:

#include <iostream>
#include <cstring>
#include <string>
using namespace std;

namespace DAY11_DEEP_CPY
{
    class Employee {
     public:
        char* name;
        int age;
        char* position;
        string objName;
    public:
        Employee(const char* n, int a, const char* p,string objname){

            this->name = new char[strlen(n) + 1];
            this->position = new char[strlen(p) + 1];
            this->age = a;
            this->objName = objname;
            strcpy(name, n);
            strcpy(position, p);
            cout << "构造函数 初始化参数" << endl;
        }

        // 深拷贝构造函数
        Employee(const Employee& other) : age(other.age), name(new char[strlen(other.name) + 1]), position(new char[strlen(other.position) + 1]),objName(other.objName) {
            strcpy(name, other.name);
            strcpy(position, other.position);
            cout << "构造函数 深拷贝" << endl;
        }
    
        ~Employee() {
            
            delete[] this->name;
            delete[] this->position;
            cout << this->objName << "析构函数" << endl;
            
            
        }
    
        void print() const 
        {
            cout << "name: " << this->name << " age:" << this->age << " position: " << this->position << endl;
        }
    };
};



int main(int argc, char *argv[])
{

    cout << "\n \n \n";
    cout << "***************************************************深拷贝解析***************************************************" << endl;
    cout << "\n \n \n";
   // 深拷贝
   {
        using namespace DAY11_DEEP_CPY;
        char hu1[] = "Tom";
        char hu2[] = "ShangHai";
        int ku1 = 23;

        char hu11[] = "Hubery";
        char hu22[] = "BeiJing";
        int ku11 = 28;

        cout << "*******step1*******" << endl;
        Employee t1(hu1,ku1,hu2,"t1");
        cout << "*******step2*******" << endl;

        Employee t2 = t1;

        cout << "*******step3*******" << endl;

        strcpy(t2.name,hu11);
        strcpy(t2.position,hu22);
        t2.objName = "t2";
        t2.age = ku11;

        cout << "t1:********* " << endl;
        t1.print();

        cout << "t2:********* " << endl;
        t2.print();
        cout << "*******step4*******" << endl;
        
    }

    return 0;
}

        运行结果:

a7aafa5adc314746b4972261f10cdd54.png

        根据结果可以看出,对t2实例中指针指向空间内容的修改并没有改变实例t1中的任何值。由此可见此段代码实现了深拷贝。

        总的来讲,C++中默认的拷贝是浅拷贝,就是仅仅就是类中成员变量的直接复制,这点对于赋值运算符也是一样的。要想实现深拷贝,需要自己构建深拷贝构造函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1315072.html

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

相关文章

Zabbix监控系统部署与管理

zabbix介绍 zabbix是⼀个基于 Web 界面的提供分布式系统监视以及网络监视功能的企业级的免费开源解决⽅案。zabbix能监视各种⽹络参数&#xff0c;保证服务器系统的安全运营&#xff1b;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 zabbix构成 zabbix由…

从纸笔到屏幕:我的CS笔记记录体验分享

前言 三年大学生活里&#xff0c;我花了很多时间在记录笔记上&#xff0c;也因为现有种类繁多的各种学习方式&#xff0c;和朋友一起走了很多弯路。纸笔&#xff0c;OneNote&#xff0c;Typora…… 想总结分享一下自己大学期间的学习笔记记录方式&#xff08;主要针对计算机学…

后端项目全局异常处理-使用RuntimeException自定义异常异常分类简单举例

接上篇&#xff1a;后端项目操作数据库-中枢组件Service调用Mapper 自定义异常&#xff1a; 手动抛出异常&#xff0c;为了后续统一捕获&#xff0c;需要异常自定义&#xff1b; 如&#xff1a;当使用抛出异常的方式表示“操作失败”时&#xff0c;为了后续统一捕获&#xff0c…

苹果计划将全球1/4的IPhone产能转移至印度

KlipC报道&#xff1a;据相关人士报道&#xff0c;苹果希望在未来2到3年内每年在印度生产超过5000万部iphone&#xff0c;要是该计划得以实现&#xff0c;印度将占领全球iPhone产量的四分之一。 KlipC的分析师Alex Su表示&#xff1a;“此次iPhone15推出是苹果印度制造计划的一…

YOLOv8重要模块解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 yolov8的模块代码主要在./ultralytics/nn/modules/下 conv.py 在conv.py文件的头部&#xff0c;__all__中声明了当前模块的所有暴露的模…

LeetCode刷题日志-73矩阵置零

思路一&#xff1a; 用一个同样大小的矩阵记录0的位置&#xff0c;然后遍历矩阵置0&#xff0c; 空间复杂度为O&#xff08;mn&#xff09; class Solution {public void setZeroes(int[][] matrix) {int [][] matrix_new new int[matrix.length][matrix[0].length];for(int …

太阳光模拟器助力植物生长研究领域的分析研究

概述 太阳光模拟器是一种精密的光学设备&#xff0c;能够模拟太阳光的波长、强度和分布&#xff0c;为植物生长提供所需的光照环境。在植物生长研究中&#xff0c;太阳光模拟器被广泛应用于探讨植物生长、发育和产量等方面的问题&#xff0c;为农业生产、园艺研究和植物科学提供…

论文阅读——Mask DINO(cvpr2023)

DINO是检测&#xff0c;Mask DINO是检测分割。 几个模型对比&#xff1a; 传统的检测分割中&#xff0c;检测头和分割头是平行的&#xff0c;Mask DINO使用二分图匹配bipartite matching提高匹配结果的准确性。 box对大的类别不计算损失&#xff0c;因为太大了&#xff0c;会…

Windows安装Tesseract OCR与Python中使用pytesseract进行文字识别

文章目录 前言一、下载并安装Tesseract OCR二、配置环境变量三、Python中安装使用pytesseract总结 前言 Tesseract OCR是一个开源OCR&#xff08;Optical Character Recognition&#xff09;引擎&#xff0c;用于从图像中提取文本。Pytesseract是Tesseract OCR的Python封装&am…

23款奔驰C260L升级香氛负离子 淡淡的幽香

相信大家都知道&#xff0c;奔驰自从研发出香氛负离子系统后&#xff0c;一直都受广大奔驰车主的追捧&#xff0c;香氛负离子不仅可以散发出清香淡雅的香气外&#xff0c;还可以对车内的空气进行过滤&#xff0c;使车内的有害气味通过负离子进行过滤&#xff0c;达到车内保持清…

2021年数维杯国际大学生数学建模C题冠状病毒下的旅游业未来发展规划求解全过程文档及程序

2021年数维杯国际大学生数学建模 C题 冠状病毒下的旅游业未来发展规划 原题再现&#xff1a; 旅游业是一个具有高度关联性的复合型产业。它不仅与交通运输业、餐饮业、旅游服务业直接相关&#xff0c;而且与第三产业的大部分行业密切相关。旅游业带动的消费支出主要包括三部分…

springAop有哪五种通知类型?可根据图标查看!

Spring AOP的通知类型有以下几种&#xff08;后面是图标变化&#xff09;&#xff1a; 1.Before通知&#xff1a; 在目标方法执行前执行。 上白下红&#xff0c;方法前执行。 2.After通知&#xff1a; 在目标方法执行后&#xff08;无论是否发生异常&#xff09;执行。 图标…

静态路由的原理和配置

一.路由器的工作原理 首先我们知道路由器是工作在网络层的&#xff0c;那就是三层设备。网络层的功能主要为&#xff1a;不同网段之间通信、最佳路径选择也就是逻辑地址&#xff08;ip地址&#xff09;寻址、转发数据。 1.路由器是什么 路由器是能将数据包转发到正确的目的地…

不同的葡萄酒瓶盖会影响葡萄酒饮用的体验

首先&#xff0c;不同的葡萄酒瓶盖会影响我们找到想要喝的葡萄酒的难易程度。螺旋盖、Zork瓶塞和起泡酒“蘑菇形瓶塞”赢得了直接的满足感&#xff0c;它们只需要拔瓶塞不需要开瓶器。来自云仓酒庄品牌雷盛红酒分享对于所有其他的酒瓶封口&#xff0c;我们都需要一个工具来打开…

【Java JVM】实例对象内存布局

当 Java 应用启动后, 基本就是在不断的创建对象, 回收对象的过程中。 而这些创建的对象基本都是存放在应用的堆 (heap) 中, 但是这些对象在堆中又是什么样子的呢? 在这篇文章中, 我们分析一下 Java JVM 中实例对象的内存布局。 在 HotSpot 虚拟机里, 对象在堆内存中的存储布局…

240Wqps,美团用户中台, 如何使用DDD架构?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#x…

Canal实时同步MySQL数据到ES

一、canal简介 canal主要用途是对MySQL数据库增量日志进行解析&#xff0c;提供增量数据的订阅和消费&#xff0c;简单说就是可以对MySQL的增量数据进行实时同步&#xff0c;支持同步到MySQL、Elasticsearch、HBase等数据存储中去。 早期阿里巴巴因为杭州和美国双机房部署&…

LabVIEW实时建模检测癌细胞的异常

LabVIEW实时建模检测癌细胞的异常 癌症是全球健康的主要挑战之一&#xff0c;每年导致许多人死亡。世界卫生组织指出&#xff0c;不健康的生活方式和日益严重的环境污染是癌症发生的主要原因之一。癌症的发生通常与基因突变有关&#xff0c;这些突变导致细胞失去正常的增长和分…

深度探索Linux操作系统 —— 构建根文件系统

系列文章目录 深度探索Linux操作系统 —— 编译过程分析 深度探索Linux操作系统 —— 构建工具链 深度探索Linux操作系统 —— 构建内核 深度探索Linux操作系统 —— 构建initramfs 深度探索Linux操作系统 —— 从内核空间到用户空间 深度探索Linux操作系统 —— 构建根文件系统…

漏刻有时数据可视化Echarts组件开发(42)动态创建DIV容器

效果展示 引入外部文件 <script src"js/jquery.min.js"></script><script type"text/javascript" src"js/echarts.5.4.3.min.js"></script>CSS层叠样式表 实现一行3列效果&#xff0c;自动换行&#xff1b; .ecbox {he…