【C++从练气到飞升】03---构造函数和析构函数

 

 🎈个人主页:库库的里昂
收录专栏:C++从练气到飞升
🎉鸟欲高飞先振翅,人求上进先读书

目录

⛳️推荐

一、类的6个默认成员函数

二、构造函数

1. 构造函数的概念

2. 构造函数的定义

3. 构造函数的特性

三、析构函数

1. 析构函数的概念

2. 析构函数的特性


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站

一、类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数

二、构造函数

1. 构造函数的概念

如下Date类,没有初始化打印出来就会是随机值,同时对于栈没有初始化,就会报错
那如果想能否在创建对象的同时,就将信息设置进去呢。因此,就有了构造函数。以Date类为例:

#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();//没有调用Init初始化函数
	Date d2;
	d2.Init(2022, 7, 6);//调用Init初始化函数
	d2.Print();
	return 0;
}
 

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

2. 构造函数的定义

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

3. 构造函数的特性

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

  • 🌏函数名与类名相同。
  • 🌏无返回值。(不需要写void)
  • 🌏对象实例化时编译器自动调用对应的构造函数。

对于上述代码所运行后的结果没有初始化d1结果是随机值,然后对比下述代码(同样没有初始化d1)及结果;运行结果自动初始化为1/1/1不是随机值且打印了Date(),这就说明对象实例化时编译器自动调用对应的构造函数

#include<iostream>
using namespace std;
class Date
{
public:
	构造函数
	Date()
	函数名与类名相同
	且无返回值
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}
  • 🌏构造函数可以重载。(本质可以写多个构造函数,提供多种初始化方式)
#include<iostream>
using namespace std;
class Date
{
public:
	1. 无参的构造函数
	Date()
	{}
	也可以写成下面这种
	Date()//函数名与类名相同且无返回值
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}
	2. 有参的构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	3. 全缺省的构造函数
	Date(int year=1, int month=1, int day=1)
	无参和全缺省的不能同时存在会存在调用歧义
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;调用无参的构造函数
	//Date func();
	这也可以是一个函数声明所以为了区分不能加()
	d1.Print();
	Date d2(2023,8,28);调用带参的构造函数
	d2.Print();

	对于全缺省的构造函数使用更灵活可以传一个参数,两个等
	Date d3(2023);
	d3.Print();
	Date d4(2023, 8);
	d4.Print();
	return 0;
}

🌟注意:如果通过无参构造函数创建对象时,对象后面不用跟括号(例如Date d1() 是错误的 ),否则就成了函数声明以下代码的函数:声明了d1函数,该函数无参,返回一个日期类型的对象warning C4930: “Date d1(void)”: 未调用原型函数(是否是有意用变量定义的?)

  • 🌏如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
 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;
 int _month;
 int _day;
 };
  
  int main()
 {
 Date d1;
 return 0;
 }

将Date类中构造函数注释后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数;将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用

🌟解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,所有类型的指针都是内置类型

#include<iostream>
using namespace std;
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}
 

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

#include<iostream>
using namespace std;
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	
	这个地方不是初始化而是声明,声明给的缺省值,默认生成的构造函数就会用这个缺省值初始化
	int _year = 2023;
	int _month = 9;
	int _day = 5;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。它们都有一个共同的特点:可以不用传参。默认构造函数只能有一个,前面两个,在语法上可以构成函数重载,但是在无参调用的时候,会发生歧义,出现调用不明确。

注意:要把默认构造函数和默认成员函数区分清楚,默认成员函数是我们不写编译器会自动生成的,默认构造函数是不需要传参的构造函数。编译器生成的构造函数,既是默认构造函数,同时也是默认成员函数。

🌟为什么上述说内置类型用的缺省值

#include<iostream>
using namespace std;
class Date
{
public:
	Date()
	{
		这里_year没有给值而_month _day给了值打印出来是2023/2/1所以声明那给的是缺省值
		
		_month = 2;
		_day = 1;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year = 2023;
	int _month = 9;
	int _day= 5;
};

int main()
{

	Date d1;
	d1.Print();
	return 0;
}
 

🌟编译器生成的默认构造的特点:

🌟总结:
一般情况下,都需要我们自己写构造函数,决定初始化方式;成员变量全是自定义类型,可以考虑不写构造函数

三、析构函数

1. 析构函数的概念

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

2. 析构函数的特性

析构函数是特殊的成员函数,其特征如下:
  • 🌏析构函数名是在类名前加上字符 ~。
  • 🌏无参数无返回值类型。
  • 🌏一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  • 🌏对象生命周期结束时,C++编译系统系统自动调用析构函数

温馨提示:析构函数不能重载。
🌟后定义先析构

#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;
	}
	~Date()
	{
		cout << "Date()" << endl;
	}
private:
	int  _year;
	int _month;
	int _day;
};
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;
};
int main()
{
	Date d1;

	Stack st1;
	Stack st2;//后定义的先析构
	return 0;
}
 
 

Stack中的成员变量a、capacity、top都是内置类型,对象st1生命周期结束要销毁的时候,a和capacity和top是在栈上不需要资源清理,最后由系统将其内存回收,而a指向的空间是在堆区上申请的,这块空间不会随着对象生命周期的结束而自动释放,所以会造成内存泄漏,因此在对象销毁前,要通过析构函数去释放成员变量a指向的空间,这就是析构函数的作用。

  • 🌏 关于编译器自动生成的析构函数,是否会完成一些事情呢?

下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数

#include<iostream>
using namespace std;
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;
}
程序运行结束后输出:~Time()
在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数;如果类中没有申请资源时(在堆上申请空间),析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date(日期)类;有资源申请时,一定要写,否则会造成内存泄漏,比如Stack类。
 

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python环境下一维时间序列的高斯均值滤波分解方法

信号分解是一种可以将复杂的观测信号分解为若干子信号的时频分析技术。该技术可以通过分解得到的子信号来有效表征观测信号内部的时频特性&#xff0c;进而能够从观测信号中提取出有用信息。因此&#xff0c;信号分解在信号处理领域中发挥着重要的作用。 傅里叶分解是早期常用…

论文学习——基于枢轴点预测和多样性策略混合的动态多目标优化

论文题目&#xff1a;A dynamic multi-objective optimization based on a hybrid of pivot points prediction and diversity strategies 基于枢轴点预测和多样性策略混合的动态多目标优化&#xff08;Jinhua Zheng a,b,d, Fei Zhou a,b,∗, Juan Zou a,b, Shengxiang Yang a…

mysql数据库(下)

目录 约束 约束的概念和分类 1、约束的概念&#xff1a; 2、约束的分类 1、主键约束 2、默认约束 3、非空约束 4、唯一约束 5、外键约束 约束 约束的概念和分类 1、约束的概念&#xff1a; 约束时作用于表中列上的规则&#xff0c;用于限制加入表的数据约束的存在保证…

Java学习笔记------常用API(二)

Object 无有参构造 public Object() 空参构造 成员方法&#xff1a; public String toString() 返回对象的字符串表示 public boolean equals(object obj) 比较两个对象是否相等 Object默认用号比较地址值&#xff0c;需要重写才能比较属性值 protected O…

知识文档管理系统平台:企业管理的王炸

无论是企业内部的文件共享&#xff0c;还是团队之间的协作编辑&#xff0c;知识文档管理系统都能发挥巨大的作用。它帮助企业整理、存储和查找各种文档资料&#xff0c;这不仅能提高企业的工作效率&#xff0c;还能增强企业的竞争力。今天就跟着LookLook同学一起来深入了解知识…

4款好用的AI写作软件推荐,让你成为写作大神

写作已经成为我们日常生活和工作中必不可少的一部分&#xff0c;当我们在还绞尽脑汁的去想如何写作的时候&#xff0c;别人已经弯道超车用上了人工智能写作软件&#xff0c;今天&#xff0c;小编想为大家推荐4款好用的AI写作软件&#xff0c;让你在几秒钟内生成高质量文章的同时…

ADGUARD规则备份

ADGUARD规则备份 文章目录 ADGUARD规则备份使用方法规则 使用方法 规则 123pan.com###app > div.ant-spin-nested-loading.global-loading > div.ant-spin-container > div > div.appdiv.web-wrap > div.banner-container-pc:nth-child(3) bilibili.com###comm…

【Typescript】any和unknown的区别

any----没有任何约束【JavaScript中基本都是 any类型&#xff0c;可随意赋值】 unknow----可以接受任何类型的赋值&#xff0c;但不可以赋值给其他任意类型【除非使用as断言转换】 let o:number[] [1,2,3]// 可接收任何类型 let a:unknown o// 不可传递给其他类型 let x:st…

基于SpringBoot的“实习管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“实习管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统首页界面图 学生注册界面图 后台登录界面图 …

本地mysql5.7以上版本配置及my.ini

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 往期热门专栏回顾 专栏…

当页面滚动到指定位置时,底部按钮出现并固定到底部——js基础积累

需求场景&#xff1a; 当页面滚动到指定位置时&#xff0c;底部按钮出现并固定到底部。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><metaname"viewport"content"widthdevice-width, user…

Jetpack Compose 动画正式开始学习

1. 简单值动画 //将一个Color简单值 从一个值 变化到另一个 另一个简单值 就用 animateColorAsStateval backgroundColor by animateColorAsState(if (tabPage TabPage.Home) Purple100 else Green300) 动画其实就是 一个状态不停发生改变导致 组件不断重组产生的过程 2.…

iPhone, Android 手机是如何收到推送通知的?

本文转自 公众号 ByteByteGo&#xff0c;如有侵权&#xff0c;请联系&#xff0c;立即删除 iPhone, Android 手机是如何收到推送通知的&#xff1f; 我们的手机或电脑是如何收到推送通知的&#xff1f; 通常我们可以使用消息解决方案 Firebase 来支持通知推送。下图显示了 Fi…

刷题DAY21 | LeetCode 530-二叉搜索树的最小绝对差 501-二叉搜索树中的众数 236-二叉树的最近公共祖先

530 二叉搜索树的最小绝对差&#xff08;easy&#xff09; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 思路&#xff1a;双指针法 代码实现&#xff1a; class So…

ArmSoM Rockchip系列产品 通用教程 之 RTC 使用

1. RTC 简介​ RTC&#xff1a;(Real_Time Clock)&#xff1a;实时时钟 HYM8563是一种低功耗实时时钟&#xff08;RTC&#xff09;芯片&#xff0c;用于提供精确的时间和日期信息。它提供一个可编程的时钟输出&#xff0c;一个中断输出和一个掉电检测器&#xff0c;所有的地址…

从零开始,一步步构建服务网格istio

一、环境情况 环境&#xff1a;Ubuntu20.04 机器数量&#xff1a;单机1台 IP&#xff1a;10.9.2.83 二、准备知识 为什么使用 Istio&#xff1f; Istio提供了一种更高级别的服务网格解决方案&#xff0c;它可以简化和加强 Kubernetes 集群中的服务间通信、流量管理、安全…

SpringBoot配置加载顺序和SpringBoot分离打包:将jar包与lib依赖、配置文件分开

文章目录 一、SpringBoot配置加载顺序1.SpringBoot配置优先级&#xff08;1&#xff09;命令行参数&#xff08;2&#xff09;配置文件 二、SpringBoot分离打包&#xff1a;将jar包与lib依赖、配置文件分开1.pom文件配置2.打包后的目录结构 一、SpringBoot配置加载顺序 官方文…

下载文件,无法获取header中的Content-Disposition

问题&#xff1a;axios跨域请求时&#xff0c;无法获取header中的Content-Disposition&#xff0c;并且network中已显示Content-Disposition 在使用CORS方式跨域时&#xff0c;浏览器只会返回默认的头部Header 解决&#xff1a; 后端在返回时&#xff0c;需要设置公开的响应…

C++第三弹---C++入门(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 C入门 1、内联函数 1.1、概念 1.2、特性 2、auto关键字(C11) 2.1、类型别名思考 2.2、auto简介 2.3、auto的使用细则 2.3、auto不能推导的场景 …

Transformer家族

在《Transformer原理》中我们介绍了&#xff0c;现在很多大模型都是基于Transformer&#xff0c;其中最出名就是GPT和BERT模型&#xff0c;在GPT和BERT模型被提出来之后&#xff0c;NLP领域也出现了基于Transformer结构的模型&#xff0c;按照模型结构基本可以分为三类&#xf…