普通类型对象之间的复制很简单,而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量,这篇文章将帮你理清C++类对象的拷贝方式

拷贝构造函数,拷贝赋值运算符

首先我们简单了解下默认的拷贝构造函数和拷贝赋值运算符

拷贝构造函数

第一个参数是自身类类型引用,其他参数都有默认值的构造函数就是拷贝构造函数

class Sales_data {
public:
Sales_data(); //默认构造函数
Sales_data(const Foo&); //默认拷贝构造函数
//...
};

拷贝构造函数用来初始化非引用类类型参数,所以拷贝构造函数自己的参数必须是引用类型(如果不是引用:为了调用拷贝构造函数,必须拷贝它的实参,为了拷贝实参,又需要调用拷贝构造函数,无限循环)

合成拷贝构造函数(默认)

和默认构造函数一样,编译器会帮你定义一个默认拷贝构造函数(如果你没有手动定义的话),不同的是,如果你定义了其他构造函数,编译器还是会给你合成一个拷贝构造函数

举个例子:Sales_data的合成拷贝构造函数等价于

class Sales_data {
public:
Sales_data();
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
}; Sales_data::Sales_data(const Sales_data& origin) :
bookNo(origin.bookNo), //使用string的拷贝构造函数
units_sold(origin.units_sold), //拷贝
revenue(origin.revenue) { //拷贝
//空函数体
}

直接初始化,拷贝初始化

通过以下几行代码不难理解

string dots(10,'.');				//直接初始化
string s(dots); //直接初始化
string s2 = dots; //拷贝初始化
string null_book = "9-999-9999-9" //拷贝初始化
string nines = strings(100,'9'); //拷贝初始化

使用直接初始化时,我们是在要求编译器使用普通的函数匹配,来选择与我们提供的参数最匹配的构造函数

使用拷贝初始化时,我们要求编译器将右侧运算符对象拷贝到正在创建的对象中(需要的话还进行类型转换

拷贝赋值运算符

赋值运算符本质也是函数,它由operator关键字后面接要定义的运算符的符号组成,赋值运算符就是一个名为operator=的函数,和其他函数一样,它也有一个返回类型和一个参数列表

参数表示运算符的运算对象,某些运算符(包括赋值运算符)必须定义为成员函数,如果一个运算符是成员函数,则其左侧运算对象就能绑定到隐式的this参数上,对于一个二元运算符(例如赋值运算符),右侧运算对象就会作为显示参数传递

拷贝赋值运算符接受一个与其所在类相同类型的参数

class Sales_data {
public:
Sales_data& operator=(const Sales_data&);
};

为了与内置类型的赋值保持一直,赋值运算符通常返回一个指向其左侧运算对象的引用

合成拷贝赋值运算符(默认)

和拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会生成一个合成拷贝赋值运算符,类似拷贝构造函数,对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值

拷贝赋值运算符会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,对于数组类型的成员,逐个赋值数组元素合成拷贝赋值运算符返回一个指向其左侧运算对象的引用

Sales_data& Sales_data::operator=(const Sales_data& rhs) {
bookNo = rhs.bookNo;
units_sold = rhs.units_sold;
revenue = rhs.revenue;
return *this;
}

浅拷贝

回头看看我们最初的Sales_data类

class Sales_data {
public:
Sales_data();
Sales_data(const Sales_data&);
private:
std::string bookNo;
int units_sold = 0;
double revenue = 0.0;
};

以下这样的初始化看似没有什么问题

int main()
{
Sales_data data1;
Sales_data data2 = data1;
}

下面给出一个和Sales_data不太一样的Array类

class Array
{
public:
Array() //构造函数
{
m_iCount = 5;
m_pArr = new int[m_iCount];
}
Array(const Array& rhs) //拷贝构造函数(相当于默认拷贝构造函数)
{
m_iCount = rhs.m_iCount;
m_pArr = rhs.m_pArr;
}
private:
int m_iCount;
int* m_pArr;
};

(这里的拷贝构造函数其实相当于编译器合成的默认拷贝构造函数)

我们用同样的方式初始化的时候:

int main()
{
Array array1;
Array array2 = array1;
}

默认拷贝构造函数可以完成对象的数据成员简单的复制,但是由于我们这里有一个指针类型的成员变量m_pArr,直接使用默认拷贝就会出现一个问题,两个对象的m_pArr指针指向了同一块区域

当对象arr2通过对象arr1初始化,对象arr1已经申请了内存,那么对象arr2就会指向对象arr1所申请的内存,如果对象arr1释放掉内存,那么对象A中的指针就是野指针了,这就是浅拷贝

深拷贝

为了避免这样的内存泄露,拥有指针成员的对象进行拷贝的时候,需要自己定义拷贝构造函数,使拷贝后的对象指针成员拥有自己的内存地址

class Array {
public:
Array() {
m_iCount = 5;
m_pArr = new int[m_iCount];
}
Array(const Array& rhs) {
m_iCount = rhs.m_iCount;
m_pArr = new int[m_iCount];
for (int i = 0; i < m_iCount; i++)
{
m_pArr[i] = rhs.m_pArr[i];
}
}
private:
int m_iCount;
int* m_pArr;
};

对比一下

  • 浅拷贝也叫位拷贝,拷贝的是地址
  • 深拷贝也叫值拷贝,拷贝的是内容

深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝

C++类拷贝控制 深拷贝 浅拷贝的更多相关文章

  1. C++拷贝构造函数(深拷贝&浅拷贝)

    对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.下面看一个类对象拷贝的简单例子. ...

  2. python的拷贝(深拷贝和浅拷贝)

    今天看了几篇关于python拷贝的博文,感觉不太清楚,所以我就自己做实验试一下,特此记录. 拷贝是针对组合对象说的,比如列表,类等,而数字,字符串这样的变量是没有拷贝这一说的. 实现拷贝有: 1.工厂 ...

  3. 【转】 c++拷贝构造函数(深拷贝,浅拷贝)详解

     c++拷贝构造函数(深拷贝,浅拷贝)详解 2013-11-05 20:30:29 分类: C/C++ 原文地址:http://blog.chinaunix.net/uid-28977986-id-3 ...

  4. c++拷贝构造函数(深拷贝,浅拷贝)详解

    一.什么是拷贝构造函数      首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: ; int b=a;   而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.  下面 ...

  5. 【C++】拷贝构造函数(深拷贝,浅拷贝)详解

    一.什么是拷贝构造函数  首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: ; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量. 下面看一个类对 ...

  6. c++中拷贝构造函数,浅拷贝和深拷贝的区别

    在C++提供了一种特殊的构造函数,称为拷贝构造函数.拷贝构造函数具有一般构造函数的所有特性,其作用是使用一个已经存在的对象(由拷贝构造函数的参数指定的对象)去初始化一个新的同类对象,即完成本类对象的复 ...

  7. 拷贝构造函数(深拷贝vs浅拷贝)

    拷贝构造函数(深拷贝vs浅拷贝) 类对象之间的初始化是由类的拷贝构造函数完毕的.它是一种特殊的构造函数,它的作用是用一个已知的对象来初始化还有一个对象.假设在类中没有显式地声明一个拷贝构造函数.那么, ...

  8. C++的那些事:类的拷贝控制

    1,什么是类的拷贝控制 当我们定义一个类的时候,为了让我们定义的类类型像内置类型(char,int,double等)一样好用,我们通常需要考下面几件事: Q1:用这个类的对象去初始化另一个同类型的对象 ...

  9. OOP3(继承中的类作用域/构造函数与拷贝控制/继承与容器)

    当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内.如果一个名字在派生类的作用域内无法正确解析,则编译器将继续在外层的基类作用域中寻找该名字的定义 在编译时进行名字查找: 一个对象.引用或指针的 ...

随机推荐

  1. Scrapy爬虫框架学习

    一.Scrapy框架简介 1. 下载页面 2. 解析 3. 并发 4. 深度 二.安装 linux下安装 pip3 install scrapy windows下安装 a.pip3 install w ...

  2. mysql datetime timestamp区别

    timestamp 支持数据库级UTC 时区 datetime 不支持  timestamp占4个字节 datetime占8个字节 timestamp所能存储的时间范围为:'1970-01-01 00 ...

  3. 你真的了解setState()吗?

    React 中 setState()详细解读 对于 setState() 相信伙伴们都用过,它是 React 官方推荐用来更新组件 state 的 API,但是对于 setState() 你真的了解吗 ...

  4. Vue实现静态数据分页

    <div style="padding:20px;" id="app"> <div class="panel panel-prima ...

  5. OPC协议

    详解OPC协议-工业控制和自动化领域的接口标准     摘要:OPC全称是OLEforProcessControl,即用于过程控制的OLE,是针对现场控制系统的一个工业标准接口,是工业控制和生产自动化 ...

  6. python+unittest框架第六天unittest之优化测试报告

    今天的内容主要是,用第三方的HTMLRUNner 第三方的报告来优化之前第五天批量执行案例的测试报告.案例的部分看第五天的批量执行笔记~ HTMLRUNner他可以生成更美观的测试报告,基于前辈造的车 ...

  7. MSIL实用指南-生成内部类

    生成内部类用TypeBuilder的DefineNestedType方法,得到另一个TypeBuilder.内部类的可访问性都是TypeAttributes的“Nested”开头一些成员.实例代码:y ...

  8. 本地搭建持续集成(AzureDevops)

    下载地址:https://visualstudio.microsoft.com/zh-hans/downloads/ 首先你需要SQL2017以上版本 ,不支持以下版本 完成下载之后进行安装(可选中文 ...

  9. zstu19一月月赛 duxing201606的原味鸡树

    duxing201606的原味鸡树 题意: 给定一颗有n(n<=1e9)个节点的完全二叉树,1e5次询问,问某个节点有几个子节点. 思路: 自己在月赛上没有思路,问了zfq才知道. 设两个指标, ...

  10. acm未解之谜-洛谷P1109学生分组

    把每一组的学生个数调度到一个给定区间范围内: 看了一圈题解,大佬都对原因避而不答: #include <iostream> #include <algorithm> using ...