普通类型对象之间的复制很简单,而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量,这篇文章将帮你理清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. 盘一盘 NIO (三)—— Selector解析

    Selector是个啥? Selector是Java NIO核心组件中的选择器,用于检查一个或多个Channel(通道)的状态是否处于可读.可写.实现一个单独的线程可以管理多个channel,从而管理 ...

  2. Linux Web集群架构详细(亲测可用!!!)

    注意:WEB服务器和数据库需要分离,同时WEB服务器也需要编译安装MySQL. 做集群架构的重要思想就是找到主干,从主干区域向外延展. WEB服务器: apache nginx  本地做三个产品 de ...

  3. 随笔编号-06 MYSQL数据库相关知识合集

    1  MYSQL取得某一范围随机数: 关键词:RAND() [产生0~1之间的随机数] mysql> SELECT RAND( ), RAND( ), RAND( ); +----------- ...

  4. Redis学习总结(一)--Redis入门

    Redis 概念 1.Redis 是什么 Redis 是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理. 2.Redis 能干什么 Redis 支持字符串.哈希 ...

  5. Spring学习之旅(五)--AOP

    什么是 AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善. OO ...

  6. 【解决】TLS/SSLError问题导致无法使用pip或conda安装软件包

    Copy these files from the ./Library/bin to ./DLLs/ :libcrypto-1_1-x64.*libssl-1_1-x64.* 解决 欢迎关注↓↓↓ 头 ...

  7. Codeforces 1009D

    题意略. 思路: 可知对于一个拥有n个点的图来说,它至少需要有n - 1条边来维持连通性,而且数字1恰好与后面的n - 1个数字互质: 至于n个点的图可以产生合法的互质边的个数的上限,我们可以通过莫比 ...

  8. Codeforces 255C

    题意略. 本题考查动态规划,顺便考查一下优化. 这个题目可以归约到最长递增子序列那一类,定义状态:dp[i][j] --- 当前以第i个数结尾,前一个数是第j个数的最长序列. if(a[i] == a ...

  9. Java多线程之线程协作

    Java多线程之线程协作 一.前言 上一节提到,如果有一个线程正在运行synchronized 方法,那么其他线程就无法再运行这个方法了.这就是简单的互斥处理. 假如我们现在想执行更加精确的控制,而不 ...

  10. Spring框架入门之基于Java注解配置bean

    Spring框架入门之基于Java注解配置bean 一.Spring bean配置常用的注解 常用的有四个注解 Controller: 用于控制器的注解 Service : 用于service的注解 ...