普通类型对象之间的复制很简单,而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量,这篇文章将帮你理清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. Python模块之requests,urllib和re

    目录 一.爬虫的步骤 二.使用Jupyter 三.爬虫请求模块之urllib 四.爬虫请求模块之requests 五.爬虫分析之re模块 一.爬虫的步骤 1.发起请求,模拟浏览器发送一个http请求 ...

  2. textRNN & textCNN的网络结构与代码实现!

    1. 什么是textRNN textRNN指的是利用RNN循环神经网络解决文本分类问题,文本分类是自然语言处理的一个基本任务,试图推断出给定文本(句子.文档等)的标签或标签集合. 文本分类的应用非常广 ...

  3. Linux查找命令对比(find、locate、whereis、which、type、grep)

    //太长不看版find查找磁盘空间,相较于locate和whereis速度较慢.find和locate的查找单位为文件或者目录,locate其实是find -name的另一种写法.locate和whe ...

  4. 读书分享全网学习资源大合集,推荐Python学习手册等三本书「01」

    0.前言 在此之前,我已经为准备学习python的小白同学们准备了轻量级但超无敌的python开发利器之visio studio code使用入门系列.详见 1.PYTHON开发利器之VS Code之 ...

  5. 【原创】Linux cpuidle framework

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. MSIL实用指南-生成while语句

    本篇讲解怎样生成while语句.while语句是编程语言中很重要的循环语句,它的结构是while(<表达式>) <语句或语句块> 当表达式的结果为true时就一直执行语句或语句 ...

  7. c#中的委托01

    delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型. 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联. 你可以通过委托实例调用方法. 委托用于将方法作为参数 ...

  8. python控制窗口移动(画圆)

    import win32con import win32gui import time import math notepad = win32gui.FindWindow("Notepad& ...

  9. P2564 [SCOI2009]生日礼物 贪心

    https://www.luogu.org/problemnew/show/P2564) 题意 有n个珠子,包括k中颜色,找出长度最小的一个区间,使得这个区间中包含所有的颜色. 思路 把n个珠子按照位 ...

  10. 创建最简单的exe形式COM组件并在MFC程序调用

    来新公司学习接手新项目,拿到代码打开解决方案看到里面竟然有40几个工程,有点吃惊.具体看代码也有很多之前没见过的写法,上了几天火. 有件事就没太搞明白,按照文档的说法上层很多软件都要调用IO服务器,但 ...