C++_构造函数与析构函数

       

目录

1、构造函数的写法

1.2 构造函数优化写法

2、默认构造函数与默认成员函数

2.1 默认成员函数对不同类型的处理

3、对内置类型的补丁

4、析构函数

4.1 析构函数的写法

5、默认析构函数

6、初始化列表

6.1 初始化列表的写法

6.2 初始化列表的作用

 6.3 回顾与总结

 结语:


前言:

        构造函数和析构函数都是属于类中的成员函数,在实例化一个对象时系统会自动调用该对象的构造函数,因此他常用于初始化对象成员变量,一个对象在其生命周期中只会调用一次构造函数。而析构函数与构造函数作用”相反“,他是在对象销毁时自动被系统调用的,所以他的任务是清理、释放对象申请的空间资源,一个类中只能有一个析构函数。

1、构造函数的写法

        1、构造函数作为成员函数因此他必须写在类中。

        2、构造函数的函数名必须与类名一模一样。

        3、构造函数是不写返回类型的。

        4、构造函数可以实现函数重载,即一个类可以有多个构造函数。

        5、构造函数的形参可有可无,具体根据需求。

        鉴于以上无点,可以先初步认识构造函数的写法 :

#include<iostream>
using namespace std;

class Date
{
public:

	Date()//构造函数1(无参)
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}

	Date(int year, int month, int day)//构造函数2(有参)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//实例化对象就会自动调用构造函数,因此在实例化时可以执行直接传参
	Date dt1;//调用时没有传递实参,调构造函数1
	Date dt2(2022,2,22);//调用时传递了实参,则调构造函数2

	return 0;
}

        通过调试可以观察到对象dt1和dt2具体调用的是哪个构造函数:

        可以看到dt1实例化时没有传实参,因此dt1调用的是无参构造函数。而dt2传了实参(2022,2,22),因此dt2调用的是有参构造函数,他们的成员变量的值都是调用了对应的构造函数而得来的。

1.2 构造函数优化写法

        以上两个构造函数可以利用全省参数的概念将他们合成一个构造函数,而且同样可以实现两个构造函数的功能。

        优化代码如下:

#include<iostream>
using namespace std;

class Date
{
public:

	Date(int year=0, int month=0, int day=0)//写成全缺省的形式,并且缺省参数赋值为0
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date dt1;//若不传实参,则dt1的成员依旧初始化为0 0 0
	Date dt2(2022,2,22);//传实参多少则dt2的成员初始化为多少

	return 0;
}

        注意:若写成全省参数的形式,则就不能够再写构造函数1(无参写法),两种形式的构造函数只能存在一个。因为当对象实例化并且是无参调用时,系统会不知道调用全省参数形式的函数还是构造函数1(无参写法)的函数,调用会出现歧义。

2、默认构造函数与默认成员函数

        以上代码的构造函数都是自己写出来的,因此这种形式的构造函数又称为显式构造。如果我们自己不写构造函数,系统也会自动生成一个无参构造函数,又叫默认构造函数,该形式的构造函数称为隐式构造。当然,如果我们已经写了构造函数,那么系统就不会再自动生成默认构造函数了。注:我们一般把系统自动生成的默认构造函数叫做默认成员函数

        构造函数关系图:

        可以发现默认构造函数的特点:只有无参调用才能调用默认构造函数。 

        以下代码就是调用默认成员函数的例子:

#include<iostream>
using namespace std;

class Date
{
public:
        //没有显式构造                       
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date dt1;
	
	return 0;
}

        可以通过调试窗口观察dt1的成员变量的值变化:

        可以看到即使我们没有写构造函数,dt1成员变量的值也发生了改变,这就是因为系统自动调用了自己生成的默认成员函数,因此使dt1成员变量的值发生了改变,侧面也可以证实默认成员函数的存在。注意:因为默认成员函数是无参的,因此实例化对象时不能传参数。

        但是这里即使调用了系统自动生成的默认成员函数,dt1成员初始化的值依然是随机值,这么一看默认成员函数还不如我们自己手写的构造函数有用,这里生成随机值的原因是默认成员函数对不同的成员变量类型有着不同情况的处理。

2.1 默认成员函数对不同类型的处理

        首先在C++中把类型分成了两大类,一个是内置类型(即int、char...和各种指针类型)、一个是自定义类型,就是由程序员用struct、class、union..定义的自定义类型。默认成员函数对内置类型的成员变量不做任何处理,这也是为什么上面的dt1成员会是随机值。然而默认成员函数对自定义类型成员变量处理是:会调用该成员变量自己的默认构造函数

        默认成员函数对自定义类型处理的代码例子:

#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
{
public:

private:
	int _year;
	int _month;
	int _day;

	Time t;//Date类中有一个成员变量的类型是自定义类型Time
};

int main()
{
	Date dt1;
	
	return 0;
}

        运行结果:

         从结果可以看到,在屏幕上打印了Time类中的默认构造函数里的打印内容,而且Date类里是没有写任何构造函数的,说明Date类中自己生成了构造函数并且让成员变量t调用了Time中的默认构造。注意:若dt2实例化时不能传实参,因为要调用默认构造函数必须是无参调用

3、对内置类型的补丁

        由于默认成员函数对内置类型是不做任何处理的,因此内置类型再进行声明的时候可以对其赋值,但是这种赋值看起来像对成员进行初始化,实则上只是缺省值的一种写法。

        对内置类型声明时进行赋值的测试代码如下:

#include<iostream>
using namespace std;

class Date
{
public:

	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	Date()
	{
		_year = 2023;
	}

private:
	//成员声明时进行赋值
	int _year=2022;
	int _month=2;
	int _day=22;
};

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

	return 0;
}

        运行结果:

         从结果中可以得出,当在声明中给_year赋值2022时,由于我们又写了一个默认构造函数把_year的值改成了2023,导致最终打印的是默认构造函数中的2023,这里可以理解为没有用到_year的缺省值。然而在声明时给_month和_day赋予的值,并没有在构造函数中对他们的值进行修改,以至最后打印出2和22,这里可以理解为用到了_month和_day缺省值。因此把给声明中给成员变量赋值的操作叫做给成员变量赋缺省值。

4、析构函数

        析构函数与构造函数作用”相反“,他是在对象销毁时自动被系统调用的,所以他的任务是清理、释放对象申请的空间资源。

4.1 析构函数的写法

        1、析构函数的函数名规定要在类名的前面加个‘~’字符构成析构函数名。

        2、一个类只能存在一个析构函数。

        3、析构函数在对象销毁时由系统自动调用。

        4、析构函数也是不写返回类型的。

        5、一般是涉及到空间的开辟和释放的时候才会手动写析构函数,内置类型只要出了栈帧就会自动销毁。

        6、析构函数不能带有形参。

        析构函数测试代码:

#include<iostream>
using namespace std;

class Stack
{
public:

	Stack(int n=4)//构造函数
	{
		cout << "Stack(int n=4)" << endl;//观察系统是否调用该函数
		_arr = (int*)malloc(sizeof(int) * n);
		if (_arr == nullptr)
		{
			perror("malloc");
			return;
		}
		_Top = 0;
		_capacity = n;
	}

	~Stack()//析构函数
	{
		cout << "~Stack()" << endl;//观察系统是否调用该函数
		free(_arr);
		_arr = nullptr;
		_Top = _capacity = 0;
	}

private:
	int* _arr;
	int _Top;
	int _capacity;
};

int main()
{
	Stack st1;

	return 0;
}

        运行结果:

         从结果可以看到,在对象st1销毁时系统会自动调用析构函数。

5、默认析构函数

        默认析构函数即我们不写析构函数时,编译器也会自动生成一个默认析构函数。默认析构函数同样对内置类型的成员变量不做处理,对自定义类型的成员变量会调用该成员的析构函数。因此涉及到空间资源时就必须要手动写析构函数了,比如上述的析构函数测试代码,如果不写析构函数,那么系统是不会处理_arr申请的空间的,就会引发内存泄漏的问题。

        默认析构函数调用测试:

#include<iostream>
using namespace std;

class Stack
{
public:

	Stack(int n=4)//构造函数
	{
		cout << "Stack(int n=4)" << endl;//观察系统是否调用该函数
		_arr = (int*)malloc(sizeof(int) * n);
		if (_arr == nullptr)
		{
			perror("malloc");
			return;
		}
		_Top = 0;
		_capacity = n;
	}

	~Stack()//析构函数
	{
		cout << "~Stack()" << endl;//观察系统是否调用该函数
		free(_arr);
		_arr = nullptr;
		_Top = _capacity = 0;
	}

private:
	int* _arr;
	int _Top;
	int _capacity;
};

class MyQueue
{
	Stack st1;//st1和st2是MyQueue类中的自定义类型的成员变量
	Stack st2;
};

int main()
{
	MyQueue mq;

	return 0;
}

        运行结果:

        从结果可以看出,在MyQueue这个类中是没有写析构函数的,因此编译器会自动生成一个默认析构函数,并且对自定义类型的成员处理是:调用该自定义类型的析构函数。 

6、初始化列表

        严格来说,之前讲到在构造函数内进行初始化,这一操作并不是真正意义上的初始化,只是一种赋值行为,因为在构造函数内可以进行多次的赋值,然而初始化的真正含义是每个成员、对象只有一次初始化的机会。因此在构造函数内还隐藏了一部分,该部分就是初始化列表,而不论构造函数的初始化列表有没有内容,系统都会自动遍历一遍初始化列表。

6.1 初始化列表的写法

        初始化列表存在于构造函数的“中间”,即函数+形参和构造函数的大括号“{}”的中间部分, 以冒号开始,逗号进行分隔成员变量,每个成员变量后面跟小括号,小括号内就是要初始化的内容,而且每个成员变量只能写一次,对应每个成员的初始化只有一次机会。

        体现初始化列表的代码如下:

#include<iostream>
using namespace std;

class Date
{
public:
	//初始化列表的形式
	Date()
	:_year(2022)//对_year成员进行初始化,值为2022
	,_month(2)
	,_day(22)
	{}

	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

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

	return 0;
}

        运行结果:

        从结果可以看到这么写也可以进行对成员变量的赋值,其实这才是正在意义上的初始化。当然,初始化列表不仅仅只有以上作用。

6.2 初始化列表的作用

        因为有三种情况的成员变量是必须通过初始化列表完成赋值:

        1、引用成员变量

        2、被const修饰的成员变量

        3、没有默认构造函数的自定义类型成员变量

        首先第一种情况和第二种情况的示例图:

        可以看到,错误的原因在于a和b作为引用和被const修饰过的变量,在声明的时候就必须要进行初始化,这也是引用和const关键字的规定。

        解决方法:在初始化列表为a和b进行初始化即可:

class Date
{
public:
	//初始化列表的形式
	Date()
		:_year(2022)//对_year成员进行初始化,值为2022
		, _month(2)
		, _day(22)
		, b(_year)//b作为a的引用
		, a(10)//a的值初始化为10
	{}

	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	
private:
	const int a;
	int& b;

	int _year;
	int _month;
	int _day;
};

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

	return 0;
}

        第三种情况的示例图:

        上述错误的原因在于Stack类中没有默认构造函数,而MyQueue类里的自定义类型成员就无法调用Stack类里的默认构造函数了。因此该情况的解决方法是:在MyQueue类中的初始化列表进行对成员st1和st2的初始化,并且小括号内要给一个值作为“实参”传递给形参int n。

        代码如下:

#include<iostream>
using namespace std;

class Stack
{
public:

	Stack(int n)//构造函数
	{
		cout << "Stack(int n=4)" << endl;//观察系统是否调用该函数
		_arr = (int*)malloc(sizeof(int) * n);
		if (_arr == nullptr)
		{
			perror("malloc");
			return;
		}
		_Top = 0;
		_capacity = n;
	}

	~Stack()//析构函数
	{
		cout << "~Stack()" << endl;//观察系统是否调用该函数
		free(_arr);
		_arr = nullptr;
		_Top = _capacity = 0;
	}

private:
	int* _arr;
	int _Top;
	int _capacity;
};

class MyQueue
{
public:
	//在MyQueue类里使用初始化列表对st1和st2进行初始化
	MyQueue()
		:st1(4)//把4当成实参传给Stack(int n)
		,st2(4)
	{}
private:
	Stack st1;
	Stack st2;
};

int main()
{
	MyQueue mq;

	return 0;
}

 6.3 回顾与总结

        之前我们提到过当手动写了一个构造函数后,系统则不会在生成默认成员函数,那么如果在MyQueue类中写一个构造函数,且该构造函数内什么都不写,那么按理来说系统就不会去调用自己生成默认成员函数,也就不会对自定义类型的st1和st2进行处理。

        测试在MyQueue类中写一个空白的构造函数,观察编译器是否会去调用自定义类型的默认构造函数:

#include<iostream>
using namespace std;

class Stack
{
public:

	Stack(int n = 4)//Stack类中的构造函数
	{
		cout << "Stack(int n=4)" << endl;//观察系统是否调用该函数
		_arr = (int*)malloc(sizeof(int) * n);
		if (_arr == nullptr)
		{
			perror("malloc");
			return;
		}
		_Top = 0;
		_capacity = n;
	}

private:
	int* _arr;
	int _Top;
	int _capacity;
};

class MyQueue
{
public:
	MyQueue()//在MyQueue类中自己定义一个构造函数
	{
		;
	}
private:
	Stack st1;
	Stack st2;
};

int main()
{
	MyQueue mq;

	return 0;
}




         运行结果:

        从结果得出,编译器竟然还是去调用了st1和st2的默认构造函数,原因是初始化列表在起作用,虽然没在初始化列表中明确的对成员进行初始化,但是编译器还是会遍历初始化列表,所以可以理解成在遍历初始化列表的时候发生了让自定义类型成员变量去调用他的默认构造这一动作。

 结语:

        以上就是关于C++_构造函数和析构函数的讲解,构造函数和析构函数的重点在于捋清默认成员函数和默认构造函数之间的关系,其中的细节非常之多,这也正是该知识点复杂的地方。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!谢谢大家!!

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

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

相关文章

引迈信息-JNPF平台怎么样?值得入手吗?

目录 1.前言 2.引迈低代码怎么样&#xff1f; 3.平台亮点展示 4.引迈产品特点 5.引迈产品技术栈&#xff1a; 1.前言 低代码是近几年比较火的一种应用程序快速开发方式&#xff0c;它能帮助用户在开发软件的过程中大幅减少手工编码量&#xff0c;并通过可视化组件加速应用…

高效电商策略:小红书集成CRM与广告推广无代码化

无代码开发的优势 随着科技的不断进步&#xff0c;无代码开发&#xff08;No-Code Development&#xff09;已经成为快速构建系统和应用的新趋势。无代码开发指的是不需要传统编程知识&#xff0c;通过图形化的用户界面和模型驱动逻辑来创建应用程序。这种方式让非技术背景的用…

排序 | 冒泡插入希尔选择堆快排归并计数排序

排序 | 冒泡插入希尔选择堆快排归并计数排序 文章目录 排序 | 冒泡插入希尔选择堆快排归并计数排序冒泡排序插入排序希尔排序选择排序堆排序快速排序--交换排序三数取中快速排序hoare版本快速排序挖坑法快速排序前后指针法 快速排序--非递归实现归并排序归并排序非递归实现非比…

Window操作系统发展史

引言 当谈及计算机操作系统的丰富历史和多样性时&#xff0c;Windows操作系统无疑是其中的一颗璀璨明星。自1985年首次亮相以来&#xff0c;Windows经历了长足的发展&#xff0c;塑造了计算机使用体验的方方面面。从初始的简单图形用户界面到如今强大而多样的功能&…

【开源工程及源码】数字孪生乡村—经典开源项目实景三维数字孪生

智慧乡村可视化平台&#xff0c;旨在通过数字化和智能化手段提升乡村管理、服务和发展水平。通过飞渡科技强大的渲染引擎&#xff0c;1&#xff1a;1 建模还原乡村全貌&#xff0c;建立起具备信息化、智能化、绿色化的智慧乡村综合管理平台。 综合态势模块下&#xff0c;可以从…

C语言——预处理详解(#define用法+注意事项)

#define 语法规定 #define定义标识符 语法: #define name stuff #define例子 #include<stdio.h> #define A 100 #define STR "abc" #define FOR for(;;)int main() {printf("%d\n", A);printf("%s\n", STR);FOR;return 0; } 运行结果…

模板方法模式(行为型)

目录 一、前言 二、模板模式 三、带钩子的模板模式 四、总结 一、前言 模板方法模式是一种行为型设计模式&#xff0c;它定义了一个操作中的算法框架&#xff0c;将一些步骤延迟到子类中实现。这种模式是基于“开闭原则”的设计思想&#xff0c;即对扩展开放&#xff0c;对…

【Unity动画】综合案例完结-控制角色动作播放+声音配套

这个案例实现的动作并不复杂&#xff0c;主要包含一个 跳跃动作、攻击动作、还有一个包含三个动画状态的动画混合树。然后设置三个参数来控制切换。 状态机结构如下&#xff1a; 完整代码 using System.Collections; using System.Collections.Generic; using UnityEngine;pu…

数据挖掘-08-基于Python实现时间序列分析建模(ARIMA 模型)(包括数据和代码)

文章目录 0. 数据代码下载1. 背景描述2. 预测目的3. 数据总览4. 数据预处理4.1数据描述性统计与清洗a. 导入程序库b. 读取数据c. 查看统计信息和空值d. 查看是否有重复数据以及清理重复数据e. 空值清理f. 针对清洗后的数据进行统计分析 5. 探索性数据分析5.1 数据分析 6. 构建 …

【2023年公司智能工具降本增效分享总结】「智能工具的力量」总结分享我司通过AI提升软件开发效率与质量调研报告,问题踩坑之路

调研背景 人工智能&#xff08;AI&#xff09;已经成为当今科技发展的主要驱动力之一&#xff0c;AI在多个领域取得了显著的成果&#xff0c;包括软件开发。AI技术的应用可以帮助开发者提高代码质量、减少错误、优化资源和时间管理&#xff0c;从而提高软件开发效率。 调研目…

Knowledge Graph知识图谱—9. Knowledge Modeling

9. Knowledge Modeling & Ontology Engineering How should the knowledge in a KG be modeled? – Which classes of entities do we have? – Which relations connect them? – Which constraints hold for them? → these questions are defined in the ontology …

javacv的视频截图功能

之前做了一个资源库的小项目&#xff0c;因为上传资源文件包含视频等附件&#xff0c;所以就需要时用到这个功能。通过对视频截图&#xff0c;然后作为封面缩略图&#xff0c;达到美观效果。 首先呢&#xff0c;需要准备相关的jar包&#xff0c;之前我用的是低版本的1.4.2&…

速学数据结构 | 树 森林 二叉树 的概念详讲篇

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《速学数据结构》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! &#x1f4cb; 前言 &#x1f308;hello&#xff01; 各位宝子们大家好啊&#xff0c;关于线性表我们已经在前面更新完了…

【C++入门到精通】 线程库 | thread类 C++11 [ C++入门 ]

阅读导航 引言一、thread类的简单介绍二、线程函数详细介绍1. start() 函数&#xff08;1&#xff09;头文件&#xff08;2&#xff09;函数原型 2. join() 函数&#xff08;1&#xff09;头文件&#xff08;2&#xff09;函数原型 3. detach() 函数&#xff08;1&#xff09;头…

扫描电镜中的信号-噪声比(SNR)参数如何优化

在扫描电镜&#xff08;SEM&#xff09;中&#xff0c;信号-噪声比&#xff08;SNR&#xff09;的优化对于获得高质量的图像和可靠的数据分析至关重要。以下是一些优化SNR的方法&#xff1a; 选择适当的检测器&#xff1a;SEM通常配备了不同类型的检测器&#xff0c;如二次电子…

紫光展锐T820与飞桨完成I级兼容性测试 助推端侧AI融合创新

近日&#xff0c;紫光展锐高性能5G SoC T820与百度飞桨完成I级兼容性测试&#xff08;基于Paddle Lite工具&#xff09;。测试结果显示&#xff0c;双方兼容性表现良好&#xff0c;整体运行稳定。这是紫光展锐加入百度“硬件生态共创计划”后的阶段性成果。 本次I级兼容性测试完…

多域名https证书购买选择

多域名https证书是一种特殊的SSL证书&#xff0c;它允许一个证书同时保护多个域名&#xff0c;并且不限制域名的类型&#xff0c;可以保护多个域名和子域名&#xff0c;确保网站传输信息时不被窃取、篡改。那么我们该怎么选择符合需求的多域名https证书呢&#xff1f;今天就随S…

基于VGG-16+Android+Python的智能车辆驾驶行为分析—深度学习算法应用(含全部工程源码)+数据集+模型(一)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Pycharm 环境Android环境 相关其它博客工程源代码下载其它资料下载 前言 本项目采用VGG-16网络模型&#xff0c;使用Kaggle开源数据集&#xff0c;旨在提取图片中的用户特征&#xff0c;最终在移…

vue3 使用antd 报错Uncaught TypeError--【已解决】

问题现象 使用最基本的 ant-design-vue 按钮demo 都报错 报错文字如下 Uncaught TypeError: Cannot read properties of undefined (reading value)at ReactiveEffect.fn (ant-design-vue.js?v597f5366:6693:87)at ReactiveEffect.run (chunk-K2VKR2AM.js?v25c381c3:461:…

计算三叉搜索树的高度 - 华为OD统一考试

OD统一考试 分值: 100分 题解: Java / Python / C++ 定义构造三又搜索树规则如下: 每个节点都存有一个数,当插入一个新的数时,从根节点向下寻找,直到找到一个合适的空节点插入查找的规则是: 1.如果数小于节点的数减去500,则将数插入节点的左子树 2.如果数大于节点的数加…