【C++心愿便利店】No.6---C++之拷贝构造函数

文章目录

  • 一、拷贝构造函数的引入
  • 二、拷贝构造函数


在这里插入图片描述

👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:C++ 心愿便利店
🔑本章内容:拷贝构造函数
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


一、拷贝构造函数的引入

对于上章节的学习我们认识并了解了两大默认成员函数:构造函数和析构函数。构造函数主要用来进行对象的成员变量初始化操作,而析构函数主要用来对战斗后的战场做清理工作。当我们不写这些函数时,编译器会自动生成默认的构造与析构函数,但有时候,编译器生成的并不能满足我们对代码的需求,这就需要我们自己去写了(比如Stack类),所以要根据情况的不同而去选择性的写。
此外就引入一个问题,假设我们需要创建一个对象和已经存在的对象一模一样那应该怎么办呢?显然易见的答案就是拷贝,但真的只是简简单单的拷贝吗?通过对比如下两种类的拷贝:

1. 以日期类为例:进行的值拷贝是不会发生错误的
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream> 
#include<assert.h>
using namespace std;
class Date
{
public:Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}void Printf(){cout << _year<<"/" << _month << "/" << _day << endl;}
private:int _year = 1;int _month;int _day;
};void func1(Date d)
{d.Printf();
}int main()
{Date d1(2023, 9, 12);func1(d1);return 0;
}

请添加图片描述

2. 以栈类为例:进行的值拷贝会发现发生错误
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream> 
#include<assert.h>
using namespace std;class Stack
{
public:Stack(size_t n = 4){cout << "Stack(size_t n=4)" << endl;if (n == 0){a = nullptr;top = capacity = 0;}else{a = (int*)malloc(sizeof(int) * n);if (a == nullptr){perror("realloc fail");exit(-1);}top = 0;capacity = n;}}void Init(){a = nullptr;top = capacity = 0;}void Push(int x){if (top == capacity){size_t newcapacity = capacity == 0 ? 4 : capacity * 2;int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);if (tmp == nullptr){perror("realloc fail");exit(-1);}if (tmp == a){cout << capacity << "原地扩容" << endl;}else{cout << capacity << "异地扩容" << endl;}a = tmp;capacity = newcapacity;}a[top++] = x;}~Stack(){cout << "~Stack()" << endl;free(a);a = nullptr;top = capacity = 0;}int Top(){return a[top - 1];}void Pop(){assert(top > 0);--top;}void Destroy(){free(a);a = nullptr;top = capacity = 0;}bool Empty(){return top == 0;}
private:int* a;int top;int capacity;
};void func2(Stack s)
{}int main()
{Stack s1;func2(s1);return 0;
}

请添加图片描述

报错原因:

🌟同样的拷贝方式为什么对于日期类不会报错,而对于栈类就会报错呢?
请添加图片描述

解决方式:
  • 采用引用
void func2(Stack& s)
{引用没有值拷贝的问题,s就是s1别名,没有两个对象指向同一块空间的这种说法(这是一个对象)
}

请添加图片描述
🌟这里有一个误解:采用引用它不是也会析构吗? —> 同一个对象不会析构两次,s是s1的别名,s不析构,不调用析构函数

但是采用引用,s的修改也会影响s1,那如何让s改变且不影响s1?这就需要引入拷贝构造函数

二、拷贝构造函数

1. 拷贝构造函数的概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎
请添加图片描述

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

2. 拷贝构造函数的特征

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

  • 🌏拷贝构造函数是构造函数的一个重载形式。
  • 🌏拷贝构造函数的参数只有一个必须是同类型对象的引用使用传值方式编译器直接报错,因为会引发无穷递归调用
#include<iostream> 
using namespace std;
class Date
{
public:Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}Date(Date& d){cout << "Date(Date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}void Printf(){cout << _year<<"/" << _month << "/" << _day << endl;}
private:int _year = 1;int _month;int _day;
};int main()
{Date d1;以下两种写法是等价的Date d2(d1);  调用拷贝构造Date d3 = d1;   调用拷贝构造定义了一个日期类对象d1,然后想再创建一个和d1一模一样的日期类对象d2,也就是用d1去拷贝d2return 0;
}

😽 注意—>拷贝构造的错误写法:引发无穷递归

对于下述代码按常规理解就是创建d2对象的时候,把d1传过去,然后用形参d接收,再把d的值赋值给this指针(this指针指向的是d2,也就是赋值给了d2)并不会发现有任何错误

//Date d2(d1);
Date(Date d){_year = d._year;_month = d._month;_day = d._day;}

其实编译器是会报错因为底层发生了错误

在这里插入图片描述
😽根据下述图解来探索一下引发无穷递归的原因
在这里插入图片描述
如上图所示,执行date d2(d1);调用拷贝构造函数, d1传参给拷贝构造的形参d, 形参d在接收实参d1的时候,又要去调用拷贝构造来创建d,所以会出现 date d(d1),而拷贝的过程中又会调用自身的拷贝构造函数,会无休止的递归下去。
😽 对于上述的错误可以采用下述方法进行规避:
采用引用的方法:Date d2(d1);调用拷贝构造,d1传给了d,且d是d1的别名,this指针就是d2,这样d1就拷贝给了d2,此时就不会再去无穷无尽的调用拷贝构造

//Date d2(d1);
Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}
拷贝构造函数针对自定义类型,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数
  • 🌏若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
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类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

在这里插入图片描述
😽注意:建议写拷贝构造函数时加上const

Date(const Date& d)//d是d1的别名,权限缩小{cout << "Date(Date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}

表明拷贝构造函数中没有对传递进来的对象做任何修改,也是防止拷贝构造函数对对象进行修改,实际上不加const也是可以照常运行的。不过还是建议:不需要改变对象时,传引用时加上const

  • 🌏编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类(浅拷贝)这样的类是没必要的。但是对于栈类(深拷贝)的对象,是必须要显示写的,不然会出现析构两次的问题
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;
}

在这里插入图片描述

  • s1对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间然后里面存了4个元素12 3 4
  • s2对象使用s1拷贝构造,而Stack类没有显式定义拷贝构造函数,则编译器会给Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是按照值拷贝的,即将s1中内容原封不动的拷贝到s2中。因此s1和s2指向了同一块内存空间。
  • 当程序退出时,s2和s1要销毁。s2先销毁,s2销毁时调用析构函数,已经将0x11223344的空间释放了,但是s1并不知道,到s1销毁时,会将0x11223344的空间再释放一次,一块内存空间多次释放,肯定会造成程序崩溃。

😽注意:
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
😽综上所述栈类是要自己写拷贝构造的具体如下:
深拷贝就是去堆上重新申请一块空间,把s1中_array指向的空间中的内容,拷贝到新申请的空间,再让s2中的_array指向该空间

	Stack(const Stack& s){cout << "Stack(Stack& s)" << endl;//深拷贝_array = (DataType*)malloc(sizeof(DataType) * s._capacity);if (NULL == _array){perror("malloc申请空间失败");return;}memcpy(_array, s._array, sizeof(DataType)*s._size );_size = s._size;_capacity = s._capacity;}

😽总结:
我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性不一样:

  • 内置类型,值拷贝(浅拷贝)
  • 自定义的类型,调用他的拷贝
  • Date不需要我们实现拷贝构造,默认生成就可以用
  • Stack需要我们自己实现深拷贝的拷贝构造,默认生成会出问题(析构两次)
4. 拷贝构造函数的典型调用场景
  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}

在这里插入图片描述
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/85604.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构入门-14-排序

一、选择排序 1.1 选择排序思想 先把最小的元素拿出来 剩下的&#xff0c;再把最小的拿出来 剩下的&#xff0c;再把最小的拿出来 但是这样 空间复杂度是O(n) 优化一下&#xff0c;希望原地排序 1.1.2 选择原地排序 索引i指向0的位置 索引j指向i1的元素 j 后面的元素遍历&…

Excel 语法

目录 语法 逐步创建公式 对单元格使用公式 另一个例子 语法 Excel中的一个公式用于进行数学计算。公式总是以单元格中键入的等号开头&#xff0c;然后是您的计算。 注意&#xff1a;您可以通过选择单元格并键入等号&#xff08;&#xff09;来声明该单元格 逐步创建公式…

用PHP实现极验验证功能

极验验证是一种防机器人的验证机制&#xff0c;可以通过图像识别等方式来判断用户是否为真实用户。在实现极验验证功能时&#xff0c;您需要进行以下步骤&#xff1a; 1 注册极验账号&#xff1a; 首先&#xff0c;您需要在极验官网注册账号并创建一个应用&#xff0c;获取相应…

【Linux环境安装教程】

对于科班学生来讲&#xff0c;是不是学到哪门专业课都是需要安装环境的&#xff0c;本篇文章分享一下安装Linux环境的过程。 步骤&#xff1a; 1.准备安装所需要的工具 &#xff08;1&#xff09;安装Centos7镜像 这里呢&#xff0c;由于小编在所报的专业课班上听到老师讲过…

Linux——文件系统

✅<1>主页&#xff1a;&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;Linux——文件系统 ☂️<3>开发环境&#xff1a;Centos7 &#x1f4ac;<4>前言&#xff1a;上期我们了解了文件在内存中得组织方式&#xff0c;那么文件在磁盘中…

百度SEO优化技巧(选择、网站结构、内容优化、外链建设、数据分析)

百度关键词SEO优化介绍 SEO是搜索引擎优化的缩写&#xff0c;是指通过优化网站结构、内容和外部链接等方式&#xff0c;提高网站在搜索引擎中的排名&#xff0c;从而获取更多的访问量和流量。百度是中国最大的搜索引擎之一&#xff0c;对于企业来说&#xff0c;优化百度关键词…

在pandas中使matplotlib动态画子图的两种方法【推荐gridspec】

先上对比图&#xff0c; 第一种方法&#xff0c;这里仅展示1个大区&#xff0c;多个的话需要加一层循环就可以了&#xff0c;主要是看子图的画法 当大区下面的国家为1个或2个时&#xff0c;会进行报错 # 获取非洲国家列表 african_countries df[df[大区] 南亚大区][进口国…

招商信诺人寿基于 Apache Doris 统一 OLAP 技术栈实践

本文导读&#xff1a; 当前&#xff0c;大数据、人工智能、云计算等技术应用正在推动保险科技发展&#xff0c;加速保险行业数字化进程。在这一背景下&#xff0c;招商信诺不断探索如何将多元数据融合扩充&#xff0c;以赋能代理人掌握更加详实的用户线索&#xff0c;并将智能…

RocketMQ 源码分析——Producer

文章目录 消息发送代码实现消息发送者启动流程检查配置获得MQ客户端实例启动实例定时任务 Producer 消息发送流程选择队列默认选择队列策略故障延迟机制策略*两种策略的选择 技术亮点:ThreadLocal 消息发送代码实现 下面是一个生产者发送消息的demo&#xff08;同步发送&#…

Mallox勒索病毒:最新变种.mallox_lab袭击了您的计算机?

引言 在数字化时代&#xff0c;数据是我们生活和工作的重要组成部分&#xff0c;但同时也引发了各种网络威胁&#xff0c;.mallox_lab勒索病毒便是其中之一。这种恶意软件以其加密文件并勒索赎金的方式而闻名&#xff0c;给个人和组织带来了巨大的风险和损失。本文将深入探讨.…

400电话申请流程详解,助您快速办理联通、移动、电信400电话

导语&#xff1a;随着企业业务的发展&#xff0c;越来越多的企业开始关注400电话的申请与办理。本文将为您详细介绍联通、移动、电信400电话的申请流程&#xff0c;帮助您快速办理400电话&#xff0c;提升企业形象和客户服务质量。 一、联通400电话申请流程 咨询与选择&#x…

BUUCTF:[GYCTF2020]FlaskApp

Flask的网站&#xff0c;这里的功能是Base64编码解码&#xff0c;并输出 并且是存在SSTI的 /hint 提示PIN码 既然提示PIN&#xff0c;那应该是开启了Debug模式的&#xff0c;解密栏那里随便输入点什么报错看看&#xff0c;直接报错了&#xff0c;并且该Flask开启了Debug模式&am…

多分类中混淆矩阵的TP,TN,FN,FP计算

关于混淆矩阵&#xff0c;各位可以在这里了解&#xff1a;混淆矩阵细致理解_夏天是冰红茶的博客-CSDN博客 上一篇中我们了解了混淆矩阵&#xff0c;并且进行了类定义&#xff0c;那么在这一节中我们将要对其进行扩展&#xff0c;在多分类中&#xff0c;如何去计算TP&#xff0…

AB包的依赖关系

1、什么是依赖关系 有时候一个模型所需要的东西可能在不同的包里面&#xff0c;例如蓝色立方体的模型和材质在不同的包&#xff08;mode和head&#xff09;里&#xff0c;这时需要加载两个包才能让这个球正常显示 2、如何获取依赖关系并加载 //加载AB包 AssetBundle ab Asse…

Manifest merger failed

编译报错&#xff1a;Manifest merger failed with multiple errors 定位编译错误&#xff1a;java.lang.RuntimeException: Manifest merger failed with multiple errors 近日&#xff0c;项目中需要引入一个module。在成功导入后&#xff0c;添加依赖到主模块上&#xff0c…

《动手学深度学习 Pytorch版》 7.3 网络中的网络(NiN)

LeNet、AlexNet和VGG的设计模式都是先用卷积层与汇聚层提取特征&#xff0c;然后用全连接层对特征进行处理。 AlexNet和VGG对LeNet的改进主要在于扩大和加深这两个模块。网络中的网络&#xff08;NiN&#xff09;则是在每个像素的通道上分别使用多层感知机。 import torch fr…

科技云报道:云安全的新战场上,如何打破“云威胁”的阴霾?

科技云报道原创。 近年来&#xff0c;在云计算和网络安全产业的蓬勃发展下&#xff0c;我国云安全行业市场规模呈现高速增长态势&#xff0c;在网络安全市场总体规模中占比不断上升。 据统计&#xff0c;近5年我国云安全市场保持高速增长&#xff0c;2021年我国云安全市场规模…

(25)(25.1) 光学流量传感器的测试和设置

文章目录 25.1.1 测试传感器 25.1.2 校准传感器 25.1.3 测距传感器检查 25.1.4 预解锁检查 25.1.5 首次飞行 25.1.6 第二次飞行 25.1.7 正常操作设置 25.1.8 视频示例&#xff08;Copter-3.4&#xff09; 25.1.9 空中校准 25.1.1 测试传感器 将传感器连接至自动驾驶仪…

【C语言】指针的进阶(四)—— 企业笔试题解析

笔试题1&#xff1a; int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } 【答案】在x86环境下运行 【解析】 &a是取出整个数组的地址&#xff0c;&a就表示整个数组&#xff0c;因此…

Biome-BGC生态系统模型与Python融合技术

Biome-BGC是利用站点描述数据、气象数据和植被生理生态参数&#xff0c;模拟日尺度碳、水和氮通量的有效模型&#xff0c;其研究的空间尺度可以从点尺度扩展到陆地生态系统。 在Biome-BGC模型中&#xff0c;对于碳的生物量积累&#xff0c;采用光合酶促反应机理模型计算出每天…