普通类型对象之间的复制很简单,而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量,这篇文章将帮你理清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. centos7.x 安装系统/配置网络/设置主机名

    1.安装系统     系统的安装就不多说了,自行查找百度,如:https://www.cnblogs.com/wcwen1990/p/7630545.html   2.配置网络(局域网上网) 修改配置 ...

  2. C语言编程入门之--第五章C语言基本运算和表达式-part4

    5.3.5 和二进制极为密切的运算符 本小节的运算符需要借助二进制概念来理解. 二进制数据中,比如一个字节的数据,它的十进制为228,二进制就为11100100,如图5.11, 注意:如果不懂怎么转换 ...

  3. Java网络编程与NIO详解2:JAVA NIO 一步步构建I/O多路复用的请求模型

    微信公众号[黄小斜]作者是蚂蚁金服 JAVA 工程师,专注于 JAVA 后端技术栈:SpringBoot.SSM全家桶.MySQL.分布式.中间件.微服务,同时也懂点投资理财,坚持学习和写作,相信终身 ...

  4. C++通过宏定义判断操作系统及编译器

    INTRODUCTION: C++的编译环境千奇百怪,很多时候一些代码在某些编译环境下可用,一旦移到其他环境下,就会干脆Compile Error 对此,我们可以使用C++的宏定义来判断操作系统,从而 ...

  5. 图解Java数据结构之队列

    本篇文章,将对队列进行一个深入的解析. 使用场景 队列在日常生活中十分常见,例如:银行排队办理业务.食堂排队打饭等等,这些都是队列的应用.那么队列有什么特点呢? 我们知道排队的原则就是先来后到,排在前 ...

  6. OPC协议

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

  7. javaScript 基础知识汇总(三)

    1.循环:while 和 for while 循环 while(condition){ //代码 循环体 } do ... while  循环 let i =0; do { //循环体 }while( ...

  8. 生产环境中Redis的key的设计

    问题:如果我们需要将MySql表的数据存储到Redis中该如何存储? 例如:有t_user表 id username email  11 leo  leo@163.com  22  laymans   ...

  9. 洛谷 P2055 【假期的宿舍】

    题库 :洛谷 题号 :2055 题目 :假期的宿舍 link :https://www.luogu.org/problem/P2055 首先明确一下:校内的每个学生都有一张床(只是校内的有) 思路 : ...

  10. C 扩展对闭包特性的支持

    今日听说某君批评 C 语言说它[输入一个参数返回一个函数]很困难. 例如在 Python 中,你可以 def addn(n): def addx(x): return n + x return add ...