首页 > 编程语言 >C++中的深拷贝和浅拷贝介绍

C++中的深拷贝和浅拷贝介绍

时间:2023-09-18 21:36:24浏览次数:34  
标签:int 介绍 Base C++ time 拷贝 Array 构造函数

对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如:

C++中的深拷贝和浅拷贝介绍_数据

class Base{
    public:
        Base(): m_a(0), m_b(0){ }
        Base(int a, int b): m_a(a), m_b(b){ }
    private:
        int m_a;
        int m_b;
    };
    int main(){
        int a = 10;
        int b = a;  //拷贝
        Base obj1(10, 20);
        Base obj2 = obj1;  //拷贝
        return 0;
    }

b 和 obj2 都是以拷贝的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存,这种默认的拷贝行为就是浅拷贝,这和调用 memcpy() 函数的效果非常类似。

C++中的深拷贝和浅拷贝介绍_拷贝构造函数_02

对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。

下面我们通过一个具体的例子来说明显式定义拷贝构造函数的必要性。我们知道,有些较老的编译器不支持变长数组,例如 VC6.0、VS2010 等,这有时候会给编程带来不便,下面我们通过自定义的 Array 类来实现变长数组。

#include <iostream>
    #include <cstdlib>
    using namespace std;
    //变长数组类
    class Array{
    public:
        Array(int len);
        Array(const Array &arr);  //拷贝构造函数
        ~Array();
    public:
        int operator[](int i) const { return m_p[i]; }  //获取元素(读取)
        int &operator[](int i){ return m_p[i]; }  //获取元素(写入)
        int length() const { return m_len; }
    private:
        int m_len;
        int *m_p;
    };
    Array::Array(int len): m_len(len){
        m_p = (int*)calloc( len, sizeof(int) );
    }
    Array::Array(const Array &arr){  //拷贝构造函数
        this->m_len = arr.m_len;
        this->m_p = (int*)calloc( this->m_len, sizeof(int) );
        memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
    }
    Array::~Array(){ free(m_p); }
    //打印数组元素
    void printArray(const Array &arr){
        int len = arr.length();
        for(int i=0; i<len; i++){
            if(i == len-1){
                cout<<arr[i]<<endl;
            }else{
                cout<<arr[i]<<", ";
            }
        }
    }
    int main(){
        Array arr1(10);
        for(int i=0; i<10; i++){
            arr1[i] = i;
        }
       
        Array arr2 = arr1;
        arr2[5] = 100;
        arr2[3] = 29;
       
        printArray(arr1);
        printArray(arr2);
       
        return 0;
    }

运行结果:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

0, 1, 2, 29, 4, 100, 6, 7, 8, 9

本例中我们显式地定义了拷贝构造函数,它除了会将原有对象的所有成员变量拷贝给新对象,还会为新对象再分配一块内存,并将原有对象所持有的内存也拷贝过来。这样做的结果是,原有对象和新对象所持有的动态内存是相互独立的,更改一个对象的数据不会影响另外一个对象,本例中我们更改了 arr2 的数据,就没有影响 arr1。

C++中的深拷贝和浅拷贝介绍_拷贝构造函数_03

这种将对象所持有的其它资源一并拷贝的行为叫做深拷贝,我们必须显式地定义拷贝构造函数才能达到深拷贝的目的。

深拷贝的例子比比皆是,除了上面的变长数组类,我们在《C++ throw关键字》一节中使用的动态数组类也需要深拷贝;此外,标准模板库(STL)中的 string、vector、stack、set、map 等也都必须使用深拷贝。读者如果希望亲眼目睹不使用深拷贝的后果,可以将上例中的拷贝构造函数删除,那么运行结果将变为:

0, 1, 2, 29, 4, 100, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9

可以发现,更改 arr2 的数据也影响到了 arr1。这是因为,在创建 arr2 对象时,默认拷贝构造函数将 arr1.m_p 直接赋值给了 arr2.m_p,导致 arr2.m_p 和 arr1.m_p 指向了同一块内存,所以会相互影响。

另外需要注意的是,printArray() 函数的形参为引用类型,这样做能够避免在传参时调用拷贝构造函数;又因为 printArray() 函数不会修改任何数组元素,所以我们添加了 const 限制,以使得语义更加明确。

到底是浅拷贝还是深拷贝

如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以。

C++中的深拷贝和浅拷贝介绍_拷贝构造函数_04

另外一种需要深拷贝的情况就是在创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等,请看下面的例子:

#include <iostream>
    #include <ctime>
    #include <windows.h>  //在Linux和Mac下要换成 unistd.h 头文件
    using namespace std;
    class Base{
    public:
        Base(int a = 0, int b = 0);
        Base(const Base &obj);  //拷贝构造函数
    public:
        int getCount() const { return m_count; }
        time_t getTime() const { return m_time; }
    private:
        int m_a;
        int m_b;
        time_t m_time;  //对象创建时间
        static int m_count;  //创建过的对象的数目
    };
    int Base::m_count = 0;
    Base::Base(int a, int b): m_a(a), m_b(b){
        m_count++;
        m_time = time((time_t*)NULL);
    }
    Base::Base(const Base &obj){  //拷贝构造函数
        this->m_a = obj.m_a;
        this->m_b = obj.m_b;
        this->m_count++;
        this->m_time = time((time_t*)NULL);
    }
    int main(){
        Base obj1(10, 20);
        cout<<"obj1: count = "<<obj1.getCount()<<", time = "<<obj1.getTime()<<endl;
       
        Sleep(3000);  //在Linux和Mac下要写作 sleep(3);
       
        Base obj2 = obj1;
        cout<<"obj2: count = "<<obj2.getCount()<<", time = "<<obj2.getTime()<<endl;
        return 0;
    }

运行结果:

obj1: count = 1, time = 1488344372

obj2: count = 2, time = 1488344375

运行程序,先输出第一行结果,等待 3 秒后再输出第二行结果。Base 类中的 m_time 和 m_count 分别记录了对象的创建时间和创建数目,它们在不同的对象中有不同的值,所以需要在初始化对象的时候提前处理一下,这样浅拷贝就不能胜任了,就必须使用深拷贝了。

标签:int,介绍,Base,C++,time,拷贝,Array,构造函数
From: https://blog.51cto.com/u_15641375/7515910

相关文章

  • 自我介绍
    我是福州大学21级计算机科学与技术专业的102101118;我是不想摆烂的刘嘉峻;我平常喜欢:弹吉他,跑步,打羽毛球,健身等;我最喜欢麻辣烫(紫荆园1楼)心中有丘壑,立马定山河!   2023-09-1820:53:55......
  • 软工个人介绍
    我是102101608黄家卿!!!!!!我的爱好是画画画画画画画画各种画,剪剪剪剪剪各种影视剪辑;非常喜欢吃福大玫瑰园一楼的漳州鸭粉;怎么办我都蛮喜欢的,先推荐一首《神仙住在山林》吧,再来一首孙燕姿的《飘着》吧,再来一首林志炫的《没离开过》和《烟花易冷》,再来一首粤语歌《圆》吧,再来一首··......
  • Vue的介绍
    一 Vue简介Vue(读音 /vjuː/,类似于 view)是一套用于构建用户界面的渐进式框架与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合官网:https://cn.vuejs.org/ 二 Vue特点易用:通过HTML、......
  • 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(5) -- 树列表
    在我们展示一些参考信息的时候,有所会用树形列表来展示结构信息,如对于有父子关系的多层级部门机构,以及一些常用如字典大类节点,也都可以利用树形列表的方式进行展示,本篇随笔介绍基于WPF的方式,使用TreeView来洗实现结构信息的展示,以及对它的菜单进行的设置、过滤查询等功能的实现逻辑......
  • Lnton羚通视频分析算法开发平台关于电子封条算法监测系统的详细介绍
    Lnton羚通的算法算力云平台是一款卓越的解决方案,具备出众的特点。它提供高性能、高可靠性、高可扩展性和低成本的优势,使用户能够高效地执行复杂计算任务。此外,该平台还提供广泛的算法库和工具,并支持用户上传和部署自定义算法,以增强平台的灵活性和个性化能力。电子封条监控系统利用Y......
  • C/C++中结构体占用内存大小的计算方法
    两个值:对齐系数:一般为8个字节。#pragmapack(8)设置对齐系数为8。有效对齐值:假设结构体中最长的类型的长度为len,则有效对齐值=min(len,对齐系数)。计算规则:计算存放的位置:第一个成员放在位置0,后面的成员A存放的时候,会先计算size=min(A大小,有效对齐值),A只放在size的整数倍......
  • 商用密码体系架构介绍
    在网络安全上,采用https接入网关提供初始化验证和加密通信通道。在数据安全上,用户鉴别采用SM3、SM4等算法加密存储,采用SM3、SM4等算法对数据加密传输。在终端安全上,通过终端证书进行身份验证;在应用安全上,开展移动应用安全加固,通过数字证书、数据加密保证移动应用安全,接入时采......
  • 27、Flink 的SQL之SELECT (SQL Hints 和 Joins)介绍及详细示例(2-2)
    Flink系列文章[1、Flink部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接][13、Flink的tableapi与sql的基本概念、通用api介绍及入门示例][14、Flink的tableapi与sql之数据类型:内置数据类型以及它们的属性][15、Flink的t......
  • helm介绍
    Helm基础应用helm是个包管理工具,类似Linux操作系统。Ubuntu的apt或者centosyum的包管理工具,helm就是u我们给k8s集群加了一个对应的仓库,然后用helm命令行装软件包,通过helm命令装,他会直接装在你的k8s集群里面去,helm属于一个k8s集群的包管理工具15年年底发布的,目前三个三个版本v1v2......
  • C++ explicit
    C++explicitexplicit关键字有两个用途:指定构造函数或者转换函数(C++11起)为显示,即它不用用于隐式转换和赋值初始化。可以与常量表达式一同使用。当该表达式为true才为显示转换(C++20起)。1.将构造函数标记为显式C++中的explicit关键字通常用来将构造函数标记为显式类型转换,......