首页 > 编程语言 >C++:深拷贝和浅拷贝

C++:深拷贝和浅拷贝

时间:2023-01-05 09:55:30浏览次数:34  
标签:int C++ Base time 拷贝 Array 构造函数

1. 什么是浅拷贝和深拷贝

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

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() 函数的效果非常类似。

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

下面通过一个具体的例子来说明显式定义拷贝构造函数的必要性。我们知道,有些较老的编译器不支持变长数组,例如 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。

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

深拷贝的例子比比皆是,除了上面的变长数组类,动态数组类也需要深拷贝;此外,标准模板库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 限制,以使得语义更加明确。

2. 到底是浅拷贝还是深拷贝

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

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

#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,C++,Base,time,拷贝,Array,构造函数
From: https://www.cnblogs.com/crossoverpptx/p/17026690.html

相关文章

  • C/C++数学口算比赛系统[2023-01-04]
    C/C++数学口算比赛系统[2023-01-04]题目三数学口算比赛系统设计要求:适用于小学生数学口算比赛的系统。比赛题型分为两种:“四则简单运算”和“四则混合运算”,计算机......
  • C/C++图书管理系统[2023-01-04]
    C/C++图书管理系统[2023-01-04]17、图书管理系统主要包括管理图书的库存信息、每一本书的借阅信息以及每一个人的借书信息。每一种图书的库存信息包括编号、书名、作者、......
  • C++相关
    解释类和对象的关系,并举例说明。(1)类是对象的抽象,对象是类的具体实例。(2)学生是类型,张三是一个具体的学生。classStudent{};Studentzhangsan;构造函数的作用是什么?......
  • C/C++值班安排系统
    C/C++值班安排系统值班安排系统一、系统要求1、问题描述某公司的保安人员由于工作需要进行轮休制度,一星期中每个岗位每人休息一天。预先让每一个人选择自己认为合适的......
  • C/C++奥运会奖牌统计管理系统
    C/C++奥运会奖牌统计管理系统设计一个奥运会奖牌统计管理系统,每个国家为一条记录,包括国家名、金牌数、银牌数、铜牌数等。该系统能提供以下功能:1、系统以菜单方式进行工......
  • C++多态
    一、多态分类分为:静态多态  --> 函数重载和运算符重载      动态多态 ---> 派生类和虚函数实现运行时多态父类中同名函数定义虚函数 --> 子类......
  • C++11:move函数将左值强制转换为右值
    通过学习C++11移动构造函数我们知道,C++11标准中借助右值引用可以为指定类添加移动构造函数,这样当使用该类的右值对象(可以理解为临时对象)初始化同类对象时,编译器会优先选择......
  • Dev-c++下‘stoi‘ was not declared in this scope解决办法
    打开“工具”->“编译选项”选择“代码生成/优化”,设置成以下形式即可......
  • 浅拷贝与深拷贝
    相互关联用浅拷贝,不关联就用深拷贝。深拷贝和浅拷贝是指在赋值一个对象时,拷贝的深度不同。在进行深拷贝时,会拷贝所有的属性,并且如果这些属性是对象,也会对这些对象进行深......
  • 软件开发入门教程网 Search之C++ 动态内存
       C++基本的输入输出   ......