【C++初阶】类与对象(中)之构造函数与析构函数

news2024/11/27 23:49:22

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


【本章内容】

前言

本章是补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的。 【上期地址】

目录

  • 前言
  • 一、类的6个默认成员函数
  • 二、为什么会存在构造函数和析构函数
  • 三、构造函数的使用例子
  • 四、 构造函数特性
  • 五、析构函数的使用例子
  • 六、析构函数的特征
  • 六、总结

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

如果一个类中什么成员都没有,简称为空类。 然而,空类中真的什么都没有吗?其实并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数(不写函数,编译器会自动生成函数)。 那么类中有哪些默认成员函数呢?让我们接着往下看

在这里插入图片描述

二、为什么会存在构造函数和析构函数

在使用类的函数的时候,某些粗心的程序员可能会忘记初始化和销毁。特别是销毁,如果程序结束后没有及时销毁,可能会存在内存泄漏。为了能一劳永逸解决这个问题,C++就有了构造函数和析构函数,他可以自动完成初始化工作和销毁。

三、构造函数的使用例子

#include <iostream>
#include <stdlib.h>
using namespace std;

class Stack
{
public:

	// 成员函数
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}

	void Push(int x)
	{
		CheckCapacity();
		a[top] = x;
		top++;
	}

	void Pop()
	{
		if (Empty())
			return;
		top--;
	}

	int Top()
	{
		return a[top - 1];
	}

	int Empty() 
	{ 
		return 0 == top; 
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity;
	}

	void CheckCapacity()
	{
		if (top == capacity)
		{
			int newcapacity = capacity * 2;
			int* tmp = (int*)realloc(a, newcapacity * sizeof(int));
			if (tmp == nullptr)
			{
				return;
			}
			a = tmp;
			capacity = newcapacity;
		}
	}

private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack s1;
	//假设粗心的程序员未给栈初始化(已注释掉)
	//s1.Init();
	
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	while (!s1.Empty())
	{
		printf("%d ", s1.Top());
		s1.Pop();
	}

	printf("\n");
	return 0;
}

以上代码问题主要在于未给栈初始化,所以导致程序崩溃(这里就不展示结果了)。因此,我们可以通过使用构造函数作用:主要完成初始化工作

【构造函数代码实现】

#include <iostream>
#include <stdlib.h>
using namespace std;

class Stack
{
	
public:
	// 构造函数
	Stack(int defaultCapacity = 4)
	{
		// 构造函数的实现逻辑和初始化函数的逻辑一样
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}
	
	// 有了构造函数后,初始化函数就可以不用写了
	/*void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}*/
	
	// 栈的插入
	void Push(int x)
	{
		CheckCapacity();
		a[top] = x;
		top++;
	}

	// 出栈
	void Pop()
	{
		if (Empty())
			return;
		top--;
	}

	// 取栈顶元素
	int Top()
	{
		return a[top - 1];
	}
	
	// 判断栈是否为空
	int Empty() 
	{ 
		return 0 == top; 
	}

	// 栈的销毁
	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity;
	}
	
	// 检查栈是否需要扩容
	void CheckCapacity()
	{
		if (top == capacity)
		{
			int newcapacity = capacity * 2;
			int* tmp = (int*)realloc(a, newcapacity * sizeof(int));
			if (tmp == nullptr)
			{
				return;
			}
			a = tmp;
			capacity = newcapacity;
		}
	}

private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack s1;
	//假设粗心的程序员未给栈初始化(已注释掉)
	//s1.Init();
	
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	while (!s1.Empty())
	{
		printf("%d ", s1.Top());
		s1.Pop();
	}

	printf("\n");
	s1.Destroy();

	return 0;
}

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

【程序结果】

在这里插入图片描述

四、 构造函数特性

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

其特征如下:

  1. 函数名与类名相同。

  2. 无返回值。意思是:可以不用写返回类型

  3. 对象实例化时编译器自动调用对应的构造函数。

  4. 构造函数支持函数重载

【例如】

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

int main()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数

	
	Date d3();//错误
	// warning: 未调用原型函数(是否是有意用变量定义的?)
}

【构造函数的调用】

  1. 就是在对象后加上参数列表或者不加参数列表。如果通过无参构造函数创建对象时,对象后面不用跟括号,否则编译器无法区分是否是对象还是函数名,如d3的错误
  2. 对于函数重载,避免产生调用歧义(例子配合下面第7点使用)。重载知识点详细请参考这篇博客–>点击跳转
  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数(不传参可以直接调用的),一旦用户显式定义编译器将不再生成
#include <iostream>
using namespace std;

class Date
{
public:
	void Print()
	{
		cout << year << ' ' << month << ' ' << day << endl;
	}
private:
	int year;
	int month;
	int day;
};

int main()
{
	Date d1;
	d1.Print();

	return 0;
}

【运行结果】

在这里插入图片描述

上述类中没有显式定义构造函数,因此编译器会自动生成一个无参的默认构造函数。但其成员变量通过编辑器初始化后打印的结构确实一个随机的值,这是为什么呢?让我们接着往下看

  1. 不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?上面d1对象调用了编译器生成的默认构造函数,但是d1对象的yearmonthday却是随机值。也就说在这里编译器生成的默认构造函数并没有什么用?

解答:
在C++中,一般把类型分为两类,第一种是内置类型(基本类型),像intchardouble这些语言本身定义的类型就称为内置类型;第二种是自定义类型,其概念就是自己定义的类型,诸如用struct、class、union等定义的类型。所以,若用户不定义构造函数,编译器默认的构造函数对内置类型不做初始化处理(注意:有些编译器也会对内置类型有初始化处理,这是个性化行为,但我们还是要坚信编译器默认的构造函数对内置类型不做初始化处理),然而自定义类型会去调用编译器默认的构造函数
【总结】

  1. 当成员变量是内置类型的时候,最好自己写一个构造函数,不然如上所描述的,编译器默认的构造函数会对成员变量初始化成随机值;
  2. 若成员变量都是自定义类型,可以考虑使用编译器默认的构造函数。并且默认初始化都为0,当然也可以自己写一个构造函数,具体看要求

后来,C++11中针对编译器默认构造函数对内置类型成员不初始化的缺陷做出了优化,即:内置类型成员变量在类中声明时可以给缺省值(默认值)

#include <iostream>
using namespace std;

class Date
{
public:
	void Print()
	{
		cout << year << ' ' << month << ' ' << day << endl;
	}

private:
	int year = 20023;
	int month = 5;
	int day = 20;
};

int main()
{
	Date d1;
	d1.Print();

	return 0;
}

【程序结果】

在这里插入图片描述

注意:以上的成员变量都是变量的声明,而不是初始化。这里给的是默认缺省值,供编译器生成默认构造函数使用

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

class Date
{
public:
	//无参的构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	// 全缺省的构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_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>
#include <stdlib.h>
using namespace std;

class Stack
{
public:
	//构造函数
	Stack(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}

	
	/*void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}*/

	void Push(int x)
	{
		CheckCapacity();
		a[top] = x;
		top++;
	}

	void Pop()
	{
		if (Empty())
			return;
		top--;
	}

	int Top()
	{
		return a[top - 1];
	}

	int Empty() 
	{ 
		return 0 == top; 
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity;
	}

	void CheckCapacity()
	{
		if (top == capacity)
		{
			int newcapacity = capacity * 2;
			int* tmp = (int*)realloc(a, newcapacity * sizeof(int));
			if (tmp == nullptr)
			{
				return;
			}
			a = tmp;
			capacity = newcapacity;
		}
	}

private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack s1;
	//假设粗心的程序员未给栈初始化(已注释掉)
	//s1.Init();
	
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	while (!s1.Empty())
	{
		printf("%d ", s1.Top());
		s1.Pop();
	}

	printf("\n");

	//假设粗心的程序员未释放内存空(已注释)
	//s1.Destroy();

	return 0;
}

释放内存空间是程序员最容易忘记的一件事,未被释放的内存空间最后都会导致内存泄漏。因此,Bjarne Stroustrup大佬就对此进行了优化,就引出了构析函数。优化代码如下:

#include <iostream>
#include <stdlib.h>
using namespace std;

class Stack
{
public:
	//构造函数
	Stack(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}

	//析构函数
	~Stack()
	{
		cout << "内存空间已销毁" << endl;
		free(a);
		a = nullptr;
		top = capacity;
	}

	
	/*void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (nullptr == a)
		{
			return;
		}
		capacity = defaultCapacity;
		top = 0;
	}*/

	void Push(int x)
	{
		CheckCapacity();
		a[top] = x;
		top++;
	}

	void Pop()
	{
		if (Empty())
			return;
		top--;
	}

	int Top()
	{
		return a[top - 1];
	}

	int Empty() 
	{ 
		return 0 == top; 
	}

	/*void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity;
	}*/

	void CheckCapacity()
	{
		if (top == capacity)
		{
			int newcapacity = capacity * 2;
			int* tmp = (int*)realloc(a, newcapacity * sizeof(int));
			if (tmp == nullptr)
			{
				return;
			}
			a = tmp;
			capacity = newcapacity;
		}
	}

private:
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack s1;
	//假设粗心的程序员未给栈初始化(已注释掉)
	//s1.Init();
	
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	while (!s1.Empty())
	{
		printf("%d ", s1.Top());
		s1.Pop();
	}

	printf("\n");

	//假设粗心的程序员未释放内存空(已注释)
	//s1.Destroy();

	return 0;
}

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。为了验证“对象在销毁时会自动调用析构函数”,我在析构函数中添加了“内存空间已销毁”。最后一起来看看结果:

【程序结果】

在这里插入图片描述

六、析构函数的特征

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

  1. 析构函数的函数名与类名也是相同,但是在类名前加上字符 ~
  2. 无参数无返回值类型。因此析构函数不能重载
  3. 一个类只能有一个析构函数(不能重载)。若未显式定义,系统会自动生成默认的析构函数。注意:系统自动生成默认的析构函数对内置类型成员不做处理,而自定义类型会去调用它的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
  5. ①一般情况下,如果类中没有动态申请资源(堆区)时,析构函数就可以不写,直接使用编译器生成的默认析构函数就行;
    ②如果有动态申请资源,就需要显示写析构函数释放资源。
    ③其次,如果需要释放资源的成员都是自定义类型,不需要写析构。

    在这里插入图片描述

六、总结

构造函数和析构函数都是C++中的特殊函数。

  • 构造函数用于在创建对象时初始化对象的成员变量,它的名称与类名相同,没有返回类型,可以有参数。当对象被创建时,会自动调用构造函数。

  • 析构函数用于在对象被销毁时释放对象所占用的资源,它的名称也与类名相同,但在名称前面加上了一个波浪号(~),没有参数和返回值。当对象被销毁时,会自动调用析构函数。

  • 构造函数和析构函数都是可选的,如果不定义,编译器会自动生成默认的构造函数和析构函数。但在某些情况下,我们需要自定义构造函数和析构函数,以满足特定的需求,如初始化对象时需要进行一些特殊的操作,或者对象被销毁前需要释放一些资源。

总之,构造函数和析构函数是C++中非常重要的特殊函数,掌握它们的使用方法对于编写高质量的C++代码是至关重要的。

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

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

相关文章

C++继承详解——基类派生类对象赋值转换、菱形虚拟继承

hello&#xff0c;这里是bangbang&#xff0c;今天来讲下继承。 面向对象三大特性&#xff1a;封装、继承、多态。 目录 1. 继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1 定义格式 1.2.2 继承关系和访问限定符 1.2.3 继承基类成员访问方式的变化 2. 基类和派生类对…

Java面试知识点(全)-设计模式二

Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 13.模板模式 定义一个操作中算法的骨架&#xff0c;而将一些步骤延迟到子类中&#xff0c;模板方法使得子类可以不改变算法的结构即可重定义该算法的…

7. 深入理解MVCC与BufferPool缓存机制

MySQL性能调优 1. MVCC多版本并发控制机制1.1 undo日志版本链与read view机制详解 2. Innodb引擎SQL执行的BufferPool缓存机制 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 本节课内容&#xff1a; 1、…

PowerShell install 一键部署VMware_Workstation

VMware Workstation 前言 VMware Workstation Pro 是业界标准的桌面 Hypervisor&#xff0c;用于在 Linux 或 Windows PC 上运行虚拟机 download VMware_Workstation VMware_Workstation WindowsVMware_Workstation linux文档downloaddownload参考 前提条件 开启wmi,配置…

2023接口自动化测试框架9项必备功能(建议收藏)

当你准备使用一个接口测试框架或者自造轮子的时候&#xff0c;或许你需要先了解下一个接口自动化测试框架必须具备什么功能。 一、校验 这个很好了解&#xff0c;如果没有校验&#xff0c;单纯的执行接口的话&#xff0c;那就谈不上测试了。所以支持对返回值校验是一个必须的…

【集合详解】——python基础——如桃花来

目录索引 集合的特点&#xff1a;创建集合&#xff1a;集合常见操作&#xff1a;增加数据&#xff1a;*add():**update():* 删除数据&#xff1a;*remove():**discard():**pop():* 查找数据&#xff1a;*in:**not in:* 集合的特点&#xff1a; 没有重复元素,即使重复&#xff0…

【ONE·C++ || set和map(三):基于红黑树的封装框架】

总言 主要介绍map、set的封装框架。 文章目录 总言1、基本框架说明2、map、set封装Ⅰ&#xff1a;用于比较的仿函数3、map、set封装Ⅱ&#xff1a;迭代器实现3.1、基本说明3.2、begin()、end()、operator*、operator&、operator、operator!3.2.1、begin()、end()3.2.2、op…

( 位运算 ) 693. 交替位二进制数 / 476. 数字的补数 ——【Leetcode每日一题】

❓ 题目一 693. 交替位二进制数 难度&#xff1a;简单 给定一个正整数&#xff0c;检查它的二进制表示是否总是 0、1 交替出现&#xff1a;换句话说&#xff0c;就是二进制表示中相邻两位的数字永不相同。 示例 1&#xff1a; 输入&#xff1a;n 5 输出&#xff1a;true 解…

使用inbuilder完成UBML低代码设计

文章目录 一、活动介绍二、我所认识的低代码平台三、使用inbuilder开发工具拖拉跩实现 低代码产品开发四、环境搭建五、5分钟完成低代码实验六、财务报销报表实验活动成果截图七、总结 一、活动介绍 开放原子训练营开启inBuilder低代码实验室活动。无论您是计算机行业相关从业…

(css)el-select多选以tag展示时,超过显示长度以...省略号显示

(css)el-select多选以tag展示时&#xff0c;超过显示长度以…省略号显示 效果 代码&#xff1a; <span>系统词典维度&#xff1a;</span><el-selectv-model"dNum"placeholder"请选择"multiplecollapse-tags //设置collapse-tags属性将它…

Windows批处理指令

前言 批处理文件&#xff08;batch file&#xff09;包含一系列 DOS 命令&#xff0c;通常用于自动执行重复性任务。用户只需双击批处理文件便可执行任务&#xff0c;而无需重复输入相同指令。编写批处理文件非常简单&#xff0c;但难点在于确保一切按顺序执行。编写严谨的批处…

Java进阶-面向对象入门

1. 面向过程与面向对象 1.1 何谓“面向对象”的编程思想&#xff1f; 首先解释一下“思想”。 先问你个问题&#xff1a;你想做个怎样的人&#xff1f; 可能你会回答&#xff1a;我想做个好人&#xff0c;孝敬父母&#xff0c;尊重长辈&#xff0c;关爱亲朋…… 你看&#…

2023年Android开发者路线-第2部分

2023年Android开发者路线-第1部分 2023年Android开发者路线-第2部分 2023年Android开发者路线-第3部分 2023年Android开发者路线-第4部分 2023Android开发者路线-第2部分 在上一篇文章中&#xff0c;我们讨论了 Android 架构的重要元素&#xff0c;包括主要的 Android 语言…

探索iOS之AVFoundation框架

AVFoundation框架的业务层主要是AVKit和UIKit&#xff0c;内核层包括CoreVideo、CoreAudio、CoreMedia、VideoToolBox等。AVFoundation作为iOS的音视频框架&#xff0c;提供音视频播放、录制、编辑、编解码、音效设置等。接下来&#xff0c;让我们看一下整体的框架图。 一、AVK…

ANR基础篇 - Trace.txt文件分析

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、trace.txt文件示例二、日志分析2.1 CPU 负载2.2 内存信息2.3 堆栈信息schedst…

Mybatis 案例

文章目录 Mybatis 案例一、 准备工作1.1 数据库表1.2 Restfull规范1.3 封装结果类1.4 实体类 二、部门管理2.1 查询全部部门信息2.2 删除部门2.3 新增部门 三、员工管理3.1 分页查询3.2 分页查询 - PageHelper插件3.3 分页查询 - 条件查询3.4 批量删除员工3.5 新增员工3.6 修改…

蓝桥杯模块学习3——蜂鸣器与继电器

第一章 硬件部分 1.1 电路的组成部分 1.1.1 译码器和锁存器 具体可回顾之前LED灯的文章&#xff1a; https://blog.csdn.net/weixin_63568691/article/details/130660096 1.1.2 ULN2003达林顿管 原理图&#xff1a; 功能&#xff1a; &#xff08;1&#xff09;改变电路特性…

使用Spring初始化器创建Spring Boot项目

注&#xff1a;初始化向导需要联网创建Spring Boot项目 new project 项目创建完成 resources 文件夹中目录结构&#xff1a; static &#xff1a;保存所有的静态资文件&#xff0c; js css images templates &#xff1a;保存所有的模板页面&#xff08;Spring Boot默认j…

python3 爬虫相关学习1:安装requests模块

目录 1 安装前&#xff1a;避免python2 python3 引起的问题 2 如何安装python3 2.1 直接上python3 官网下载 2.2 或者windows的话&#xff0c;microsoft store 里也可以下载 2.3 查看python版本 3 安装requests模块 3.1 很可能安装requests模块之前会遇到报错&#xff…

linux0.12-8-11-vsprintf.c

[383页] 1、 这一小节可以不看代码如何实现&#xff0c;因为标准的C库函数&#xff1b; 2、 等自己看完的这本书&#xff0c;有兴趣过来研究研究也是可以的。 8-11 vsprintf.c程序 8-11-1 功能描述 该程序主要包括vsprintf(),用于对参数产生格式化的输出。由于该函数是C函数…