【C++杂货铺】构造函数和析构函数

news2024/10/7 2:29:19

在这里插入图片描述

文章目录

  • 一、类的六个默认成员函数
  • 二、构造函数
  • 三、析构函数

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

📖默认成员函数
用户没有显式实现,编译器会自动生成的成员函数,称为默认成员函数。
在这里插入图片描述

  • 构造函数:完成对象的初始化工作。
  • 析构函数:完成对象空间的清理工作。
  • 拷贝构造:使用同类对象初始化创建对象。
  • 赋值重载:把一个对象赋值给另外一个对象(该对象已存在)。
  • 取地址重载:获取对象的地址,这两个很少自己实现。

注意:构造和析构函数,不是创建对象和销毁对象。对象的创建和销毁都是编译器做的工作。

二、构造函数

📖为什么要有构造函数?
为了避免每次创建对象后,都要去调用专门的成员函数设置对象的信息,这样很麻烦,并且容易遗忘,那就想着能否在创建对象的同时,就将信息设置进去。因此,就有了构造函数。以日期类为例:

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.Init(2022, 7, 5);//调用初始化函数
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

如上面的代码,每次创建一个日期类对象后,都要手动的去调用Init函数,完成对象的初始化,整个过程繁琐,而且容易遗忘,为此,提出了构造函数的概念。

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

📖构造函数的特性

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

🎊示例:

 class Date
 {
  public:
      // 1.无参构造函数
      Date()
     {}
  
      // 2.带参构造函数
      Date(int year, int month, int day)
     {
          _year = year;
          _month = month;
          _day = day;
     }
  private:
      int _year;
      int _month;
      int _day;
 };
  
  void TestDate()
 {
      Date d1; // 调用无参构造函数
      Date d2(2015, 1, 1); // 调用带参的构造函数
      Date d3();
 }

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。即Date d3();是声明了一个d3函数,该函数无参,返回一个日期类对象,并不是创建了一个日期类对象d3

构造函数在语法上可以是私有的,但是在创建对象的时候就调不动了。在单例模式中,会把构造函数搞成私有,具体的我们以后再说。

📖编译器生成的构造函数
如果类中没有显式定义构造函数,则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类中的构造函数放开后,代码编译失败,因为一旦显式定义了任何构造函数,编译器将不再生成默认构造函数。而此时Date中的构造函数需要三个参数,Date d1;会去调用无参的构造函数,但是当前类中没有无参的构造函数,所以编译会报错。

📖编译器生成的构造函数干了什么?
C++中把类型分为内置类型自定义类型。内置类型就是语言提供的数据类型,如:intchar……自定义类型就是我们使用classstructunion等自己定义的类型。(所有类型的指针都属于内置类型)。

编译器生成的默认构造函数,对内置类型不做处理,对自定义类型,会去调用它的默认构造函数

🎊示例:

//先定义一个时间类
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;
}

在这里插入图片描述

上面代码,先定义了一个时间类Time,它的成员变量都是内置类型,给这个类写了一个无参的构造函数,接下来,定义了一个日期类Date,他有四个成员变量,其中_year_month_day都是内置类型,_t是自定义类型,并且,我们没有写日期类的构造函数,这意味着,在创建对象的时候,会去使用编译器生成的无参默认构造函数。

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

🎊示例:

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 = 1970;//给默认值
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述

📖默认构造函数
我们没写编译器自动生成的构造函数无参构造函数全缺省构造函数,这三种都叫做默认构造函数,它们都有一个共同的特点:可以不用传参。默认构造函数只能有一个,后面俩,在语法上可以构成函数重载,但是在无参调用的时候,会发生歧义,出现调用不明确。
在这里插入图片描述
注意:要把默认构造函数和默认成员函数区分清楚,默认成员函数是我们不写编译器会自动生成的,默认构造函数是不需要传参的构造函数。编译器生成的构造函数,既是默认构造函数,同时也是默认成员函数。

📖总结
一般情况下,都需要我们自己写构造函数。如果满足以下情况,即:内置类型的成员变量都有默认值,且初始化符合我们的要求,自定义类型都定义了默认构造,此时可以考虑不写构造函数,使用编译器自动生成的默认构造函数。自定义类型如果没有对应的构造函数,那就意味着初始化自定义类型需要传参,此时必须自己写构造函数,并且还会用到初始化列表。

三、析构函数

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

📖特性

  • 析构函数名是在类名前加上~
  • 无参数无返回值类型
  • 一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数
  • 对象生命周期结束时,C++编译器自动调用析构函数

小Tips:析构函数不能重载。

🎊示例:

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;
};
void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

在这里插入图片描述

Stack中的成员变量_array_capacity_size都是内置类型,所以在对象s生命周期结束要销毁的时候,不需要资源清理,最后系统直接将其内存回收即可,而_array指向的空间是在堆区上申请的,这块空间不会随着对象生命周期的结束而自动释放(归还给操作系统),所以_array被回收后,就找不到动态申请的那块空间,会造成内存泄漏,因此在对象销毁前,要通过析构函数去释放成员变量_array指向的空间,这就是析构函数的作用。

📖编译器生成的析构函数干了什么?
我们不写,编译器会自动生成一个析构函数。该析构函数对内置类型不做处理,对自定义类型会去调用它的析构函数

🎊示例:

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;
}

在这里插入图片描述
main方法中创建了Date对象d,而d中包含4个成员变量,其中_year_month_day、三个是内置类型成员,对象销毁时不需要资源清理,最后系统直接将其内存回收即可,而_tTime类对象,在d销毁时,要将器内部包含的Time类的_t对象销毁,所以要去调用Time类的析构函数。但是main函数中不能直接调用Time类的析构函数,实际销毁的是Date类对象d,所以编译器会调用Date类的析构函数,而Date类没有显示提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数。

📖总结
一般情况下,有动态申请资源,就需要显式的写析构函数来释放资源,没有动态申请的资源,可以不写析构函数,需要释放资源的成员都是自定义类型,也不需要写析构函数。


🎁结语:
 今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!
在这里插入图片描述

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

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

相关文章

容器化时代的领航者:Docker 和 Kubernetes 云原生时代的黄金搭档

一、Docker docker是一种开源的应用容器引擎&#xff0c;可以将应用程序和依赖打包成一个可移植的镜像&#xff0c;然后发布到任何支持docker的平台上&#xff0c;也可以实现虚拟化。docker的核心概念有三个&#xff1a;镜像&#xff08;image&#xff09;、容器&#xff08;co…

QT中QTimer的循环时间与槽函数执行时间以及在事件循环中触发,不同时间的结果分析

目录 当循环时间小于槽函数时间时&#xff1a; 当循环间隔时间大于槽函数时间时&#xff1a; 当存在两个定时器器&#xff0c;其中一个还是间隔100ms&#xff0c;另一个间隔1000ms&#xff1a; 当两个定时器的循环周期大于槽函数执行时间时 当在主程序中添加一个for循环…

怎么把视频转为gif动态图,3个方法轻松转换!

如何将视频转换为GIF动态图呢&#xff1f;相信许多人在日常聊天中喜欢使用各种有趣的表情包。每当互联网上出现一些有趣的热门视频时&#xff0c;我们也往往会看到许多相关的GIF表情包。那么我们应该如何将自己的视频或者一些有趣的视频制作成GIF动态图呢&#xff1f;下面我就为…

从C语言到C++_27(AVL树)概念+插入接口实现(四种旋转)

目录 1. AVL树的概念 2. AVL树结点和树的定义 3. AVL树的插入&#xff08;未包含旋转&#xff09; 4. AVL树的旋转 4.1 右右_左单旋 4.2 左左_右单旋 4.3 左右双旋 4.4 右左双旋 5. AVL树的验证 6. AVL树的删除(了解)和性能 7. AVL树插入验证完整代码 8. AVL树笔试…

下载pdm遇到的坑:Could not find a version that satisfies the requirement pdm

pip install pdm遇到的问题&#xff1a; Could not find a version that satisfies the requirement pdm (from versions: ) No matching distribution found for pdm检查了版本后发现&#xff0c;python版本在3.6&#xff0c;pdm不支持该版本 换成python3.7版本&#xff0c;则…

【力扣算法14】之 15. 三数之和 python

文章目录 问题描述示例1示例2示例 3提示 思路分析代码分析完整代码详细分析运行效果截图调用示例运行结果 完结 问题描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] num…

python编程语言之数据类型进阶操作

数值常用操作 python常用关于数值&#xff0c;数学常用的模块&#xff1a;math&#xff08;数学&#xff09;&#xff0c;random&#xff08;随机&#xff09;&#xff0c;numpy&#xff08;科学计算&#xff09;&#xff0c;pandas&#xff08;数据读写&#xff0c;数据分析&…

Yalmip入门教程(3)-约束条件的定义

博客中所有内容均来源于自己学习过程中积累的经验以及对yalmip官方文档的翻译&#xff1a;https://yalmip.github.io/tutorials/ 之前的博客简单介绍了约束条件的定义方法&#xff0c;接下来将对其进行详细介绍。 首先简单复习一下&#xff1a; 1.定义约束条件可以使用矩阵拼接…

如何通过nvm管理多个nodejs版本

随着前端项目的越来越多&#xff0c;不同项目使用的nodejs版本可能不一样&#xff0c;导致在切换不同项目时需要更换不同的nodejs版本&#xff0c;非常麻烦。本次推荐使用nvm进行多个nodejs版本的统一管理。 1、nvm的下载 nvm全称Node Version Manager&#xff0c;即Node版本管…

科技政策 | 2023年广东省省级企业技术中心(第22批)认定开始啦!

原创 | 文 BFT机器人 原文链接&#xff1a; http://gdii.gd.gov.cn/zwgk/tzgg1011/content/post_4218083.html 各企业请注意&#xff0c;2023年广东省省级企业技术中心&#xff08;第22批&#xff09;认定已经开始了&#xff0c;广东省工业和信息化厅接收资料截止时间为2023年…

【Java基础教程】Java学习路线攻略导图——史诗级别的细粒度归纳,持续更新中 ~

Java学习路线攻略导图 上篇 前言1、入门介绍篇2、程序基础概念篇3、包及访问权限篇4、异常处理篇5、特别篇6、面向对象篇7、新特性篇8、常用类库篇 前言 &#x1f37a;&#x1f37a; 各位读者朋友大家好&#xff01;得益于各位朋友的支持和关注&#xff0c;我的专栏《Java基础…

Python实战项目——物流行业数据分析(二)

今天我们对物流行业数据进行简单分析&#xff0c;数据来源&#xff1a;某企业销售的6种商品所对应的送货及用户反馈数据 解决问题&#xff1a; 1、配送服务是否存在问题 2、是否存在尚有潜力的销售区域 3、商品是否存在质量问题 分析过程&#xff1a; 依旧先进行数据处理 一…

vue Duplicate keys detected: ‘‘. This may cause an update error. found in

错误原因&#xff1a; 在使用v-for的时候&#xff0c;都要必须加上一个唯一的key值&#xff0c;key的值写成一样的了。所以就导致了警告。尽量不要使用index下标作为key值 换成后台数据返回的id或者i*随机数作为key值就好

linux中快速定位软件安装位置

linux中快速定位软件安装位置步骤如下&#xff1a; 根据进程的名字定位进程ID ps -ef | grep redis通过进程id查找软件安装位置 ll -l /proc/100788/cwd原理说明&#xff1a; linux中进程启动后&#xff0c;会在/proc/目录下新建进程工作目录; 目录规范为&#xff1a;/proc/…

寻找下一个生成式 AI 独角兽,亚马逊云科技创业加速器火热招募中!

生成式AI让人工智能技术又一次破圈&#xff0c;带来了机器学习被大规模采用的历史转折点。它正在掀起新一轮的科技革命&#xff0c;为人类带来前所未有的颠覆性的影响&#xff0c;而诸多创业者也应势而上&#xff0c;寻求创新机遇。生成式AI可以创造全新的客户体验、提高企业内…

将数字孪生系统接入 CesiumJS,能为智慧城市项目带来怎样的改变?

数字孪生系统接入 CesiumJS 的契机&#xff0c;正是智慧城市项目的需要。因为许多智慧城市项目中包含了大量地形、倾斜摄影、DOM、DEM 等 GIS 数据&#xff0c;那么为了能够在数字孪生系统中导入这些 GIS 数据&#xff0c;同时让这些数据在以可视化形式表现出来后&#xff0c;还…

C++-17. 电话号码的字母组合

题目来源&#xff1a;力扣 题目描述&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输…

【MySQL】数据不存在则插入

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…

全面认识二极管,一篇文章就够了

电子设计基础元器件 二极管&#xff0c;小小二极管&#xff0c;大大用途。 ... 矜辰所致目录 前言一、二极管基础知识1.1 什么是二极管1.2 二极管的组成1.3 二极管的原理 二、二极管特性2.1 伏安特性曲线图2.2 温度的影响2.3 关于击穿 三、 二极管的参数正向连续电流和平均整…

银行金融风险管理面试问题汇总(附答案)

最近有些学员在咨询换工作的事&#xff0c;包括一些金融上市公司的高管。我收集了一些金融风险管理面试问题相关资料&#xff0c;希望能帮助大家。记得收藏此文章&#xff0c;以防之后找不到文章。 风险经理识别和分析潜在的公司风险&#xff0c;并找到减少或避免风险的方法。…