C++第四弹 -- 类与对象(中上) (构造函数 析构函数 拷贝构造函数)

目录

  • 前言
  • 构造函数
    • 1. 概念
    • 2. 特征
  • 析构函数
    • 1. 概念
    • 2. 特征
  • 拷贝构造函数
    • 1. 概念
    • 2. 特征
  • 总结

前言

让我们一起揭开 C++ 对象生命周期管理的神秘面纱,掌握构造函数、析构函数和拷贝构造函数的精髓!

博客主页: 酷酷学!!!

期待更多好文, 点击关注~

构造函数

构造函数有点类似于我们C语言阶段的Init()初始化函数, 构造二字并不是进行构造, 而是用于给类进行初始化.

1. 概念

构造函数是一个特殊的成员函数, 名字与类名相同, 创建类类型对象时由编译器自动调用, 以保证每个数据成员都有一个合适的初始值, 并且在对象的整个生命周期内只调用一次. (函数结束,类就自动销毁了)

2. 特征

构造函数是特殊的成员函数, 需要注意的是, 构造函数虽然名称叫做构造, 但是构造函数的主要任务并不是开空间创建对象, 而是初始化对象.

其特征如下:

  1. 函数名与类名相同
  2. 无返回值(不需要写void)
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载
  5. 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数, 一旦用户定义编译器将不再生成
  6. C++把类型分成内置类型(基本类型)和自定义类型, 内置类型就是语言提供的数据类型, 如:int/char…, 自定义类型就是我们使用class/struct/union等自己定义的类型, 编译器生成默认的构造函数会对自身类型成员调用它的默认成员函数

注意: C++11中针对内置类型成员不初始化的缺陷, 又打了补丁, 即: 内置类型成员变量在类中声明可以给默认值

  1. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

在这里插入图片描述

自己可以定义无参构造函数和有参构造函数

#include<iostream>
using namespace std;class Date
{
public:自己定义的无参构造函数//Date()//{}自己定义的但参构造函数//Date(int year, int month, int day)//{//	_year = year;//	_month = month;//	_day = day;//}自己定义的全缺省构造函数,语法没有问题,但是调用无参构造函数是会发生歧义编译器会报错//Date(int year = 1, int month = 1, int day = 1)//{//	_year = year;//	_month = month;//	_day = day;//}private:int _year;int _month;int _day;int main()
{Date d1;//报错,调用无参构造,但是全缺省和无参只能有一个//Date d2(2015, 1, 1);return 0;
}

注意: 无参构造,全缺省和无参只能有一个

在这里插入图片描述

如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数的声明

	//Date d3();//注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数的声明

在这里插入图片描述

#include<iostream>
using namespace std;class Time
{
public:Time(){_hour = 0;_minute = 0;_second = 0;}private:int _hour;int _minute;int _second;
};class Date
{
public:自己定义的无参构造函数//Date()//{}自己定义的但参构造函数//Date(int year, int month, int day)//{//	_year = year;//	_month = month;//	_day = day;//}自己定义的全缺省构造函数,语法没有问题,但是调用无参构造函数是会发生歧义编译器会报错//Date(int year = 1, int month = 1, int day = 1)//{//	_year = year;//	_month = month;//	_day = day;//}private:int _year;int _month;int _day;Time _t;//编译器自动生成的构造函数只会初始化自定义类型,初始的方法就是调用它的默认构造函数,//默认构造函数有三个,无参构造函数,全缺省构造函数,和编译器自动生成的构造函数,简单理解不含参的构造函数//1.如果它有默认构造则不会报错,包括编译器自动生成的//2.如果它没有默认构造函数则会报错,(它如果自己定义了构造函数,编译器就不生成构造函数了)
};int main()
{Date d1;//报错,调用无参构造,但是全缺省和无参只能有一个//Date d2(2015, 1, 1);//Date d3();//注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数的声明Date d1;return 0;
}//但是编译器自动生成构造函数,只初始化内置类型,C++针对内置类型成员不初始化的缺陷,又打了补丁,
//即:内置类型成员变量在类中声明的时候可以给默认值
//总结一下:如果类中只有内置类型,自己写构造函数初始化
//如果类中只有自定义类型,使用编译器默认构造函数
//如果既有内置类型又有自定义类型,就给内置成员变量默认值,使用编译器默认构造函数

在这里插入图片描述

总结一下:
如果类中只有内置类型,自己写构造函数初始化
如果类中只有自定义类型,使用编译器默认构造函数
如果既有内置类型又有自定义类型,就给内置成员变量默认值,使用编译器默认构造函数

析构函数

1. 概念

与构造函数功能相反, 析构函数不是完成对对象本身的销毁, 局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数, 完成对象中的资源清理工作. (如malloc的空间, fopen, new, 类似于destory()的功能)

2. 特征

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
  5. 编译器生成的默认析构函数,对自定类型成员调用它的析构函数, 内置类型不做处理。
  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

在这里插入图片描述

这里如果不自己写析构函数, 编译器会默认生成, 但是对内置类型不做处理, 会造成内存泄漏

实例一:

typedef int DataType;class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...//编译器自动生成的析构函数,内置类型不做处理,自定义类型去调用它的析构~Stack(){if (_array){free(_array);//这里如果使用编译器默认析构函数,会内存泄漏_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};//对象生命周期结束时,C++编译器系统自动调用析构函数
void TestStack()
{Stack s;s.Push(1);s.Push(2);
}

编译器生成的默认析构函数,对自定类型成员调用它的析构函数,内置类型不做处理(可能导致内存泄漏)
所以,

  • 有资源需要清理,就需要写析构函数
  • 没有资源清理,或则内置类型成员无资源清理,剩下的都是自定类型不需要写析构函数

实例二:


class Time
{
public:~Time(){cout << "~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 d;return 0;
}
//对于这段代码,先分析构造函数,创建Date d对象时,调用其构造函数,没有则编译器自动构造函数,对基本类型不做处理
//自定义类型调用其默认构造函数,初始化_t,调用其默认构造函数,没有则编译器自动生成,对基本类型不处理,故_hour,
//_minute,_second都为随机值//析构函数,程序结束时自动调用其析构函数,没有则编译器自动生成,对基本类型不处理,自定义类型调用其析构函数,
//故调用_t的析构函数,输出了"~time"

拷贝构造函数

1. 概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

在这里插入图片描述

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

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

2. 特征

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

举个栗子:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:// 给缺省值int _year = 1;int _month = 1;int _day = 1;
};//比如说我们调用内置类型的时候,传值调用都是一份临时拷贝,
//这里传对象也是一样
//但是,如果我们传值调用拷贝函数,编译器会直接报错,因为会引发无穷递归调用
//void func(Date d)
void func(Date& d)
{d.Print();
}class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const 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);return 0;
}

这里可以对比内置类型理解, 传值会引发拷贝, 每次调用形式参数, 都会引发对象的拷贝, 然后会层层传值,引发对象的拷贝递归调用, 所以我们一般写拷贝构造函数时, 一般形参写传引用, 就不会发生这种问题, 当然传指针也可以, 但是每次传递都需要取地址, 未免有些麻烦, 而且不符合拷贝构造的定义.

在这里插入图片描述

  1. 若未显示定义,编译器会发生默认的拷贝构造函数, 默认的拷贝构造函数对象按内存存储按字节序进行拷贝, 这种拷贝叫做浅拷贝, 也叫做值拷贝.

但是, 如果有需要写析构的类, 一般都要写拷贝构造, 因为编译器默认的析构函数只是值拷贝.

这种就是浅拷贝
在这里插入图片描述

这种叫做深拷贝
在这里插入图片描述
举个例子

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);Date d3 = 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;
}

在这里插入图片描述

那么此时, 我们就需要自己写拷贝构造,重新开辟一块空间

Stack(const Stack& st){_array = (DataType*)malloc(sizeof(DataType) * st._capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}memcpy(_array, st._array, sizeof(DataType) * st._size);_size = st._size;_capacity = st._capacity;}

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

  1. 拷贝构造函数典型调用场景:
  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
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;
}

在这里插入图片描述

在这里插入图片描述

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

总结:

  1. 如果没有资源管理, 一般情况不需要写拷贝构造函数, 默认生成的拷贝构造就可以. 如:Date
  2. 如果都是自定义类型成员, 内置类型成员没有指向资源, 也是默认生成拷贝构造就可以. 如: MyQueue
  3. 一般情况下, 不需要显示析构函数, 就不需要写拷贝构造
  4. 如果内部有指针或者一些指向资源, 需要显示析构释放, 通常就需要显示写构造完成深拷贝. 如:Stack

总结

本文主要介绍了 C++ 中构造函数、析构函数和拷贝构造函数的概念、特征、应用场景以及注意事项。构造函数用于初始化对象,在对象创建时由编译器自动调用;析构函数用于清理对象资源,在对象生命周期结束时由编译器自动调用;拷贝构造函数用于创建与已有对象相同的新对象,在使用已存在的类类型对象创建新对象时由编译器自动调用。文章还解释了浅拷贝和深拷贝的区别,以及在处理指针和资源时需要手动实现拷贝构造函数的原因。


完, 感谢关注

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

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

相关文章

【Neo4j】实战 (数据库技术丛书)学习笔记

Neo4j实战 (数据库技术丛书) 第1章演示了应用Neo4j作为图形数据库对改进性能和扩展性的可能性, 也讨论了对图形建模的数据如何正好适应于Neo4j数据模型,现在到了该动 手实践的时间了。第一章 概述 Neo4j将数据作为顶点和边存储(或者用Neo4j术语,节点和关系存 储)。用户被定…

外卖商城平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;商家管理&#xff0c;骑手管理&#xff0c;商品类型管理&#xff0c;商品信息管理&#xff0c;订单信息管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;商品信息&#…

深入了解java锁升级可以应对各种疑难问题

对于java锁升级&#xff0c;很多人都停留在比较浅层的表面理解&#xff0c;一定程度下也许够用&#xff0c;但如果学习其中的细节&#xff0c;我们更好地理解多线程并发时各种疑难问题的应对方式&#xff01; 因此我将锁升级过程中可能涉及的大部分细节或者疑问都整合成了一篇…

后端之路——文件本地上传

一、基础原理 文件上传是一个很基础的知识点&#xff0c;尤其是本地上传&#xff0c;在现实开发基本都是云上传&#xff0c;但是作为一个基础要简单了解一下 首先前端我就不多讲解了&#xff0c;网页开发里用<form>表单可以上传文件&#xff0c;只需要加上这三属性&…

防火墙基础实验配置

一&#xff0c;实验拓扑 二&#xff0c;实验需求&#xff1a; 1.DMZ区内的服务器&#xff0c;办公区仅能在办公时间内&#xff08;9&#xff1a;00 - 18&#xff1a;00&#xff09;可以访问&#xff0c;生产区的设备全天可以访问 2.生产区不允许访问互联网&#xff0c;办公区…

如何批量更改很多个文件夹里的文件名中包含文件夹名?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

共生与变革:AI在开发者世界的角色深度剖析

在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的概念&#xff0c;而是逐步渗透到我们工作与生活的每一个角落。对于开发者这一群体而言&#xff0c;AI的崛起既带来了前所未有的机遇&#xff0c;也引发了关于其角色定位的深刻讨论——AI…

STM32-Unix时间戳和BKP备份寄存器以及RTC实时时钟

本内容基于江协科技STM32视频学习之后整理而得。 文章目录 1. Unix时间戳1.1 Unix时间戳简介1.2 UTC/GMT1.3 时间戳转换 2. BKP备份寄存器2.1 BKP简介2.2 BKP基本结构2.3 BKP库函数 3. RTC实时时钟3.1 RTC简介3.2 RTC框图3.3 RTC基本结构3.4 硬件电路3.5 RTC操作注意事项3.6 R…

修改服务器挂载目录

由于我们的项目通常需要挂载一个大容量的数据盘来存储文件数据&#xff0c;所以我们每台服务器都需要一个默认的挂载目录来存放这些数据&#xff0c;但是由于我们的误操作&#xff0c;导致挂载目录名字建错了&#xff0c;这时候后端就读不到挂载目录了&#xff0c;那我们我们的…

在mysql中delete和truncated的相同点和区别点

相同点 删除数据&#xff1a;两者都会删除表中的数据。影响数据&#xff1a;两者都不删除表结构&#xff0c;只影响表中的数据。 区别点 操作方式&#xff1a; DELETE&#xff1a;逐行删除数据&#xff0c;可以使用 WHERE 子句来指定删除的条件。如果不加 WHERE 子句&#…

NI 5G大规模MIMO测试台:将理论变为现实

目录 概览引言MIMO原型验证系统MIMO原型验证系统硬件LabVIEW通信系统设计套件&#xff08;简称LabVIEW Communications&#xff09;CPU开发代码FPGA代码开发硬件和软件紧密集成 LabVIEW Communications MIMO应用框架MIMO应用框架特性单用户MIMO和多用户MIMO基站和移动站天线数量…

Hive 高可用分布式部署详细步骤

目录 系统版本说明 hive安装包下载及解压 上传mysql-connector-java的jar包 配置环境变量 进入conf配置文件中&#xff0c;将文件重命名 在hadoop集群上创建文件夹 创建本地目录 修改hive-site.xml文件 同步到其他的节点服务器 修改node02中的配置 hive-site.xml 修改…

如何在多个服务器上安装WordPress分布式部署

许多网络主机现在保证其服务的正常运行时间为 99.9%&#xff0c;但这仍然每年最多有 8.7 小时的停机时间。 许多公司不能够承担这种风险。例如。在超级碗比赛中失败的体育新闻网站可能会失去忠实的追随者。 我们通过设置维护高可用性 WordPress分布式部署配置来帮助 WordPres…

C基础day8

一、思维导图 二、课后习题 #include<myhead.h> #define Max_Stu 100 //函数声明 //学生信息录入函数 void Enter_stu(int *Num_Stu,char Stu_name[][50],int Stu_score[]); //查看学生信息 void Print_stu(int Num_Stu,char Stu_name[][50],int Stu_score[]); //求出成绩…

latex英文转中文word,及一些latex相关工具分享

前言&#xff1a;想要转换latex生成的英文pdf文件为中文word文件 一、主要步骤 1、文字翻译&#xff1a;直接使用谷歌翻译等辅助将英文翻译成中文即可&#xff1b; 支持英文pdf文件全文翻译&#xff0c;再用迅捷PDF转换器之类的转成word&#xff0c;再手动调整。 https://app…

网络编程:各协议头(数据报格式)

一、mac头 二、ip头 protocol——tcp/udp &#xff08;7&#xff09;TTL——生存时间 三、tcp头 四、udp头

第三课网关作用

实验拓扑图&#xff1a; 基础配置&#xff1a; PC1的基础配置 PC2的基础配置&#xff1a; PC4的基础配置 AR1添加PC4网段: 并且添加pc1,pc2的网段。 并且添加pc1,pc2的网段。 原理&#xff1a;PC4先把数据交给100.100.100.1&#xff0c;交给了路由器&#xff0c;路由器再把数…

ARM学习(29)NXP 双coreMCU IMX1160学习----NorFlash 启动引脚选择

ARM学习&#xff08;28&#xff09;NXP 双coreMCU IMX1160学习----NorFlash 启动引脚选择 1、多种启动方式介绍 IMX1166 支持多组flexSPI 引脚启动&#xff0c;FlexSPI1以及FlexSPI2&#xff0c;通过boot cfg可以切换FlexSPI得实例。 每个实例又支持多组引脚&#xff0c;总共…

《Nature》文章:ChatGPT帮助我学术写作的三种方式

图片翻译 ** 文章内容** 忏悔时间&#xff1a;我使用生成式人工智能&#xff08;AI&#xff09;。尽管在学术界关于聊天机器人是积极力量还是消极力量的争论不休&#xff0c;但我几乎每天都使用这些工具来完善我所写论文中的措辞&#xff0c;并寻求对我被要求评估的工作进行替…

Postman使用教程【项目实战】

目录 引言软件下载及安装项目开发流程1. 创建项目2. 创建集合(理解为&#xff1a;功能模块)3. 设置环境变量&#xff0c;4. 创建请求5. 测试脚本6. 响应分析7. 共享与协作 结语 引言 Postman 是一款功能强大的 API 开发工具&#xff0c;它可以帮助开发者测试、开发和调试 API。…