【C++类和对象】类和对象(中):拷贝构造函数 {拷贝构造函数的概念及特征,拷贝构造函数不能使用传值传参,编译器自动生成的拷贝构造函数}

news/2024/12/22 13:46:38/

四、拷贝构造函数

4.1 概念

在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用


4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 函数名和类名相同,没有返回值。
  3. 可以使用函数法或赋值法调用拷贝构造函数。
  4. 拷贝构造函数的参数只有一个且必须是同类类型对象的引用,而且一般用const修饰以限制引用权限,防止误操作修改拷贝源的属性。
  5. 如果使用传值传参的方式编译器直接报错,因为会引发无穷递归调用。

实现日期类的拷贝构造函数

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(Date d) // 错误写法:编译报错,会引发无穷递归Date(const Date& d) // 正确写法{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;//写法一:函数法Date d2(d1);//使用d1拷贝构造d2//写法二:赋值法Date d2 = d1;return 0;
}

4.3 拷贝构造函数不能使用传值传参

传值传参的底层是在栈中开辟空间拷贝参数的值。如果拷贝构造函数的参数是同类类型对象的值,那么实例化形参就又要调用拷贝构造。这样就形成了死递归。

注意:类对象传值传参传值返回都会调用拷贝构造函数构造临时对象(出作用域还要析构)。由此可以看出,传引用比传值更高效尤其对于自定义类型,传值不仅要开空间(尤其对于深拷贝)还要调用拷贝构造函数和析构函数(空间时间消耗)代价更大。

在这里插入图片描述

提示:还可以传同类型对象的指针实现拷贝的功能,但要注意的是使用指针实现的函数不是拷贝构造函数(不符合语法);同时指针实现的拷贝函数在使用起来效果也不如引用。如:Date d2(&d1); Date d2 = &d1;


4.4 编译器自动生成的拷贝构造函数

若未显式定义,编译器会生成默认的拷贝构造函数。 默认生成的拷贝构造函数对于内置类型是逐字节拷贝的(aka 浅拷贝 or 值拷贝),而自定义类型是调用其拷贝构造函数完成拷贝的。

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数// 对于内置类型(_hour,_minute等)是按照字节方式直接拷贝的,而自定义类型(Time _t)是调用其拷贝构造函数完成拷贝的。Date d2(d1);return 0;
}

编译器自动生成的拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然,因为默认生成的拷贝构造函数不能解决深拷贝的问题

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);//调用的是默认生成的拷贝构造函数,进行值拷贝return 0;
}

编译运行上面的代码发现程序崩掉了,程序为什么会崩溃掉呢?
在这里插入图片描述

  • 注意:类中如果没有涉及资源申请时,拷贝构造函数写不写都可以,像Date类;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,像Stack类。

  • 此处默认的浅拷贝出现的问题:

    1. 由于两个对象中的指针指向同一块空间,一个对象修改会影响另外一个对象。
    2. 函数返回,调用析构函数时,对同一块内存空间free两次造成程序崩溃。

解决方法:自己显示实现深拷贝

再看下面这个例子:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}Stack(const Stack& st){//此处实现栈结构的深拷贝//........}//其他方法的实现....~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t _size;size_t _capacity;
};class MyQueue{
//对于内置类型进行值拷贝
int sz = 0;
//对于自定义类型调用它们的拷贝构造
Stack output;
Stack input;
};int main()
{MyQueue mq1;MyQueue mq2 = mq1;// 像MyQueue类型这种未直接涉及资源申请的类可以不写拷贝构造,// 但前提是其自定义类型成员中涉及资源申请的类实现了深拷贝。return 0;
}

像MyQueue类型这种未直接涉及资源申请的类可以不写拷贝构造,但前提是其自定义类型成员中涉及资源申请的类实现了深拷贝

总结:

  • 涉及资源申请的类需要显示的写拷贝构造,以实现类的深拷贝。比如:Stack,Queue
  • 未涉及资源申请的类不需要写拷贝构造,默认生成的就会完成类的值拷贝/浅拷贝。比如:Date
  • 未直接涉及资源申请的类也不需要写拷贝构造,默认生成的就会调用其自定义类型成员的拷贝构造函数。比如:Myqueue

http://www.ppmy.cn/news/48284.html

相关文章

确保实时操作系统(RTOS)设备中的数据安全

导读1月28日大家庆祝了数据保护日&#xff0c;这是一项旨在促进保护数据隐私和安全的国际活动。为了提高人们对数据保护的意识&#xff0c;讨论实时操作系统中数据安全的问题势在必行。目前非常规操作系统已被广泛使用&#xff0c;所以了解这一系统非常重要&#xff0c;尤其是涉…

ElasticSearch第十六讲 ES 索引模板Index Template与Dynamic Template

Index Template Index Templates可以帮助你设定Mappings和Settings,并按照一定的规则,自动匹配到 新创建的索引之上。模版仅在一个索引被新创建时,才会产生作用。修改模版不会影响已创建的索引,你可以设定多个索引模版,这些设置会被“merge”在一起,你可以指定“order”…

上岸美团,我的面经!

作者&#xff1a;阿秀 校招八股文学习网站&#xff1a;https://interviewguide.cn 这是阿秀的第「257」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 欢迎今年参加秋招的小伙伴加入阿秀的学习圈&#xff0c;目前已经超过 2200 小伙伴加入&#xff01;去年认真准备和走下来的基…

JUC-多线程(12. AQS)学习笔记

文章目录 1. 可重入锁1.1. 概述1.2. 可重入锁类型1.3. Synchronized 可重入实现机理 2. LockSupport2.1. LockSupport 是什么2.2. 3种线程等待唤醒的方法2.2.1 Object 的等待与唤醒2.2.2. Condition接口中的等待与唤醒2.2.3. 传统的 synchronized 和 Lock 实现等待唤醒通知的约…

二叉树的堂兄弟节点

目录 一.二叉树的堂兄弟节点 1.题目描述 2.问题分析 3.代码实现 1.BFS解法 2.DFS解法 二.二叉树的堂兄弟节点 II 1.题目描述 2.问题分析 3.代码实现 一.二叉树的堂兄弟节点 1.题目描述 在二叉树中&#xff0c;根节点位于深度 0 处&#xff0c;每个深度为 k 的节点的子…

JUC并发编程之CompletableFuture

Future future是java5新加的一个接口&#xff0c;他提供了一种异步并行计算的功能 接口定义了操作异步任务执行的一些方法&#xff0c;如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务是否执行完毕 目的&#xff1a;异步多线程执行且有返回结果&#…

腾讯云轻量级云服务器Centos7防火墙开放8080端口

腾讯云轻量级云服务器Centos7防火墙开放8080端口 一、centos7防火墙打开端口 因为Centos7以上用firewalld代替了iptables,也就是说firewalld开通了8080端口应该就行了 1.查看8080是否已经放开 sudo firewall-cmd --permanent --zonepublic --list-ports2.查看防火墙状态 s…

ETCD(四)读请求处理过程

客户端通过etcdctl执行get命令 etcdctl get name --endpoints localhost:12379,192.158.00.32:12379client端 首先是client会解析这条命令&#xff0c;包括其中的get API方法&#xff0c;key值&#xff0c;请求server地址。解析完之后etcdctl会创建一个clientv3库对象&#xf…