C++类和对象--构造函数和析构函数

news2024/11/24 23:20:46

0.前言

在我们写某些需要动态开辟内存空间的函数时候,会经常忘记初始化、销毁,而且有时候程序返回的情况很多,那么销毁函数写起来就会很繁琐,那么有没有什么办法解决这个问题呢?答案是:当然有!在C++中有两个默认构造函数–构造函数和析构函数,可以自动帮助我们完成初始化和销毁工作!🎉🎉🎉
在这里插入图片描述

1.构造函数

1.1构造函数概念

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

1.1构造函数特性

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

其特征如下:

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

一个栗子:

#include<iostream>
typedef int Datatype;
class Stack
{
public:
	Stack()
	{
		printf("Stack()\n");
		_a = (Datatype*)malloc(sizeof(Datatype) * 6);
		if (_a == NULL)
		{
			perror("Stack()::malloc");
			return;
		}
		_capacity = 6;
		_size = 0;
	}
private:
	Datatype* _a;
	int _size;
	int _capacity;
};
int main()
{
	Stack s;
	return 0;
}

代码运行的结果:
在这里插入图片描述
注意:

Stack s();//定义的时候,不能这样调用构造函数,编译无法很好区分s是对象还是函数名

4.构造函数可以重载(可以有多种初始化方式)

#include<iostream>
typedef int Datatype;
class Stack
{
public:
	Stack()
	{
		printf("Stack()\n");
		_a = (Datatype*)malloc(sizeof(Datatype) * 6);
		if (_a == NULL)
		{
			perror("Stack()::malloc");
			return;
		}
		_capacity = 6;
		_size = 0;
	}
	Stack(int capacity = 6)
	{
		printf("Stack(int capacity = 6)\n");
		_a = (Datatype*)malloc(sizeof(Datatype) * capacity);
		if (_a == NULL)
		{
			perror("Stack()::malloc");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
private:
	Datatype* _a;
	int _size;
	int _capacity;
};
int main()
{
	Stack st1(8);
	return 0;
}

代码运行的结果:
在这里插入图片描述
但是当像这样Stack st2;定义对象时,无参构造函数和缺省构造函数会造成歧义,编译器不知道该调用哪个进而引发错误
在这里插入图片描述

一般情况下我们保留含缺省参数的构造函数,方便我们可以指定申请的内存空间的大小

5.如果类中没有显示定义构造函数,则C++编译器会自动生成无参的默认构造函数,一旦用户自己显示定义编译器将不再生成!

#include<iostream>
using std::cout;
using std::endl;
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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	//将Date类中的构造函数放开,代码编译失败,因为一旦显示定义任何构造函数,编译器将不会再生成
	//无参构造函数放开后报错,类Date没有合适的默认构造函数
	Date d1;
	return 0;
}

代码编译运行的结果为:
在这里插入图片描述
6.内置类型/基本类型,即语言本身定义的基础类型int/char/double/指针类型等等,编译器不做处理,需要自己写构造函数;自定义类型:用struct/class等定义的类型,编译器默认生成构造函数。
eg:

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

调试运行之后:
在这里插入图片描述

结论:1、一般情况下,构造函数都需要我们自己写;2、不需要自己写构造函数的情况a、内置类型成员都有缺省值,且初始化符合我们的要求;b、类成员全是自定义类型的构造,且这些类型都定义默认构造。

7、无参构造函数、全缺省的构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(不传参就可以调用的就是构造函数)。

2.析构函数

2.1概念

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

2.2特性

析构函数是特殊的成员函数,其特征如下:

1.析构函数是在类名前加上字符~。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数,不能重载。
4.对象的生命周期结束时,C++编译系统自动调用析构函数。
5.一般情况下,有动态内存申请资源,就要写析构函数释放资源;没有动态内存开辟的资源,不需要写析构;需要释放的成员都是自定义类型,不需要写析构。

栗子:

#include<iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 3)
	{
		cout << "Stack(int capacity = 3)" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("Stack(int capacity = 3)::malloc");
			return;
		}
		_capacity = capacity;
		top = 0;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_capacity = 0;
		top = 0;
	}
private:
	int* _a = nullptr;
	int _capacity = 0;
	int top = 0;
};
int main()
{
	Stack st1;
	return 0;
}

代码运行的结果为:
在这里插入图片描述

3.拷贝构造函数

3.1概念

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

3.2特征

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

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

错误的栗子

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023,1,1);
	Date d2(d1);
	return 0;
}

代码编译的结果为:
在这里插入图片描述

在这里插入图片描述
C++规定:在传值传参的时候,内置类型传参直接拷贝;自定义类型必须调用拷贝构造函数完成拷贝
正确的使用:

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023,1,1);
	Date d2(d1);
	return 0;
}

代码调试运行的结果为:
在这里插入图片描述
一般加上const修饰

	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

发生如下情况编译器会进行报错

	Date(const Date& d)
	{
		d._year = _year;
		d._month = _month;
		d._day = _day;
	}

在这里插入图片描述
注意:使用传引用传参,自定义类型不会调用拷贝构造函数
3.若为显示定义,编译器会生成默认拷贝构造函数。默认的拷贝构造函数对象按内存存储(即按字节节序完成拷贝),这种拷贝叫做浅拷贝(值拷贝)。
eg1:

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//Date(const Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023,1,1);
	Date d2(d1);
	return 0;
}

代码调试运行的结果为:
在这里插入图片描述
eg2:

#include<iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 3)
	{
		cout << "Stack(int capacity = 3)" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("Stack(int capacity = 3)::malloc");
			return;
		}
		_capacity = capacity;
		top = 0;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_capacity = 0;
		top = 0;
	}
private:
	int* _a;
	int _capacity;
	int top;
};
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

代码调试运行的结果为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Stack类型进行值拷贝时存在的问题:1.会调用两次析构函数,即会对动态开辟的同一块空间释放两次;2.对一个Stack类型的对象修改会影响另一个对象。所以,我们需要自己实现进行深拷贝的构造函数。
深拷贝:

#include<iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 3)
	{
		cout << "Stack(int capacity = 3)" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("Stack(int capacity = 3)::malloc");
			return;
		}
		_capacity = capacity;
		top = 0;
	}
	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("Stack(const Stack& st)");
			return;
		}
		memcpy(_a, st._a, sizeof(int) * st.top);
		_capacity = st._capacity;
		top = st.top;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_capacity = 0;
		top = 0;
	}
private:
	int* _a;
	int _capacity;
	int top;
};
int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

代码运行的结果为:
在这里插入图片描述

在这里插入图片描述

进行深拷贝,st1\st2指向的动态内存的空间不同,正常结束会调用两次析构函数。

总结: 成员类型为内置类型的Date类型(编译器会进行浅拷贝构造)和成员类型都为自定义类型的类类型(前提为该自定义类型已经自己实现了深拷贝构造)不需要写拷贝构造函数;Stack类型需要自己深拷贝实现。
4.拷贝构造函数的使用场景:

  • 使用已存在的对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, 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 tmp(d);
	return tmp;
}
int main()
{
	Date d1(2023, 1, 1);
	Test(d1);
	return 0;
}

代码运行的结果为:
在这里插入图片描述

4.总结

本章我们一起学习了类和对象的构造函数、析构函数、拷贝构造函数等默认成员函数的相关知识,希望对大家认识C++中的类和对象由些许帮助!感谢大家阅读,如有不对,欢迎纠正!!!🎉🎉🎉

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

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

相关文章

Java类 继承

Java是一种面向对象的编程语言&#xff0c;类是Java中最基本的编程单元。一个类可以包含变量、方法和构造函数等。类的定义需要使用关键字class&#xff0c;例如&#xff1a; public class Person { private String name; private int age; public Person(St…

文心一言 VS 讯飞星火 VS chatgpt (44)-- 算法导论6.1 1题

一、在高度为h的堆中&#xff0c;元素个数最多和最少分别是多少? 文心一言&#xff1a; 在高度为 h 的二叉搜索树&#xff08;BST&#xff09;中&#xff0c;元素个数最多和最少的情况分别如下&#xff1a; 1.最多情况&#xff1a;当每个节点都包含两个子节点时&#xff0c…

CSS3-盒子模型-边距问题

1 清除默认内外边距 场景&#xff1a;浏览器会默认给部分标签设置默认的margin和padding&#xff0c;但一般在项目开始前需要先清除这些标签默认的margin和padding&#xff0c;后续自己设置 比如&#xff1a;body标签默认有margin&#xff1a;8px、p标签默…

一句提示词生成整个代码库——Gpt Engineer神级项目开源(附演示视频)

近日&#xff0c;一个名为Gpt Engineer神级项目开源&#xff0c;并迅速火爆全网。 短短几天内&#xff0c;该项目已经得到了25K的星星。 使用GPT-engineer进行人工智能软件开发&#xff0c;可以改变软件开发的未来。 在软件开发领域&#xff0c;一场巨大的革命正在开始。这一转…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(6月 22 日论文合集)

文章目录 一、检测相关(9篇)1.1 Wildfire Detection Via Transfer Learning: A Survey1.2 Polygon Detection for Room Layout Estimation using Heterogeneous Graphs and Wireframes1.3 Exploiting Multimodal Synthetic Data for Egocentric Human-Object Interaction Detec…

前端中的相关概念

谁道人生无再少&#xff0c; 门前流水尚能西。 桃花落尽胭脂透&#xff0c; 庭院无声五更鸡。 —— 杜甫《端午节》 HTML中class属性 HTML中class属性是一种用于为元素定义样式和标识的属性&#xff0c;以下是class属性的几种常见用法实例&#xff0c;包括标识元素、定义样…

利用OpenCV计算条形物体的长度

0、前言 在图像处理中&#xff0c;我们可能会遇到求一个线条长度的场景&#xff0c;比如&#xff0c;现在有一条裂缝&#xff0c;需要求其长度&#xff0c;或者有一个长条形的零件需要知道其长度。 本文利用OpenCV和skimage两个库&#xff0c;提供了一个解决方案。 1、解决步…

贪心法与动态规划的对比分析

高级算法设计课程论文 题 目&#xff1a;贪心法与动态规划的对比分析 作者姓名&#xff1a; 作者学号&#xff1a; 专业班级&#xff1a; 提交时间&#xff1a; 2023/6/3 目 录 1 引言 1 2 分析过程 2 2.1多段图的最短路径问题 2 2.2最小生成树问题 4 3动态规划与贪心法的对…

【动态规划算法练习】day3

文章目录 一、931. 下降路径最小和1.题目简介2.解题思路3.代码4.运行结果 二、64. 最小路径和1.题目简介2.解题思路3.代码4.运行结果 三、面试题 17.16. 按摩师1.题目简介2.解题思路3.代码4.运行结果 总结 一、931. 下降路径最小和 1.题目简介 931. 下降路径最小和 题目描述&…

浅析 GeoServer CVE-2023-25157 SQL注入

原创稿件征集 邮箱&#xff1a;eduantvsion.com QQ&#xff1a;3200599554 黑客与极客相关&#xff0c;互联网安全领域里 的热点话题 漏洞、技术相关的调查或分析 稿件通过并发布还能收获 200-800元不等的稿酬 更多详情&#xff0c;点我查看&#xff01; 简介 GeoServer是一个开…

十八、网络基础(一)

一、协议 &#xff08;一&#xff09;前置 协议其实是一种约定&#xff01;&#xff01;&#xff01; 计算机之间的传输媒介是光信号和电信号 , 通过 " 频率 " 和 " 强弱 " 来表示 0 和 1 这样的信息 , 要想传递各种不同的信息 , 就需要约定好双方的数据…

(自己动手开发自己的语言练手级应用)JSON(JavaScript Object Notation) 产生式(BNF)

写自己的开发语言时&#xff0c;很多人都会拿JSON当第一个练习对象 开源net json FJSON 解析工具https://dbrwe.blog.csdn.net/article/details/107611540?spm1001.2014.3001.5502 <json> :: <object> | <array> <object> :: "{" [ <me…

分布式学习第三天 nginx学习

目录 1. 一些基本概念 1.1 Nginx初步认识 1.2 正向/反向代理 1.3 域名和IP 2. Nginx 安装和配置 2.1 安装 2.2 配置 3. Nginx的使用 3.1 部署静态网页 3.2 反向代理和负载均衡 课外知识导读 1. URL和URI 2. DNS解析过程 复习 1. 一些基本概念 1.1 Nginx初步认识…

团体程序设计天梯赛-练习集L1篇②

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

在我掉入计算机的大坑并深陷其中时,一门名为“C语言”的编程语言让我沉迷

各位CSDN的uu们你们好呀&#xff0c;小雅兰好久没有更新博客啦&#xff0c;今天来小试牛刀&#xff01;&#xff01;&#xff01; 上一篇博客小雅兰是说自己原本是自动化专业的学生&#xff0c;但是因为一次偶然的机会对计算机的相关知识产生了浓厚的兴趣。那么&#xff0c;小雅…

Linux Ubuntu man文档的图文安装教程

文章目录 前言man文档的起源man文档的安装man文档的使用总结 前言 当提及"man文档"时&#xff0c;通常是指Unix和类Unix系统中的手册页&#xff08;man page&#xff09;&#xff0c;因为Linux是在Unix的基础上发展而来的操作系统&#xff0c;所以我们的Linux也有ma…

操作系统 - 内存管理

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

INDEMIND相机ROS bag包数据的回放

实验需要IMU相机&#xff0c;跑算法是在ROS下跑&#xff0c;在 ROS 系统中&#xff0c;可以使用 bag 文件来保存和恢复系统的运行状态&#xff0c;比如相机话题的 bag 包&#xff0c;然后回放用来进行联合外参标定&#xff0c;也可以使用EVO工具显示算法轨迹&#xff0c;这里记…

合宙Air724UG Cat.1模块硬件设计指南--GPIO控制

GPIO控制 简介 GPIO&#xff08;General-purpose input/output&#xff09;&#xff0c;通用型之输入输出的简称&#xff0c;可实现某时刻电平状态的输出与输入&#xff0c;即可以通过它们输出高低电平或者通过它们读入引脚的状态-是高电平或是低电平。 特性 共支持28个GPIO&a…

A Neural Conversational Model 读后感

目录 摘要 1、介绍 2、相关工作 3、模型 4、数据&#xff08;后面都是具体的东西&#xff0c;不赘述&#xff09; 5、总结 使用微软翻译得到的中文原文&#xff1a; 摘要 会话建模是自然语言理解和机器智能中的一项重要任务。尽管存在以前的方法&#xff0c;但它们通常仅…