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;//调用时没有传递实参,调构造函数1Date dt2(2022,2,22);//调用时传递了实参,则调构造函数2return 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 0Date 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++_构造函数和析构函数的讲解,构造函数和析构函数的重点在于捋清默认成员函数和默认构造函数之间的关系,其中的细节非常之多,这也正是该知识点复杂的地方。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!谢谢大家!!

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

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

相关文章

【Proteus仿真】【51单片机】电子门铃设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使共阴数码管&#xff0c;按键、无源蜂鸣器等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管默认显示第一种门铃音调&#xff0c;可通过K1键切…

flutter的SingleChildScrollView控件详解

文章目录 SingleChildScrollView的介绍和使用场景详细介绍 SingleChildScrollView的介绍和使用场景 SingleChildScrollView 是 Flutter 中的一个小部件&#xff0c;用于创建一个可滚动的单个子部件。它通常用于处理内容超出屏幕可见区域的情况&#xff0c;允许用户通过滚动来查…

5.3 Linux DNS 服务

1、概念介绍 DNS&#xff08;Domain Name System&#xff09;域名系统&#xff0c;是互联网的一项核心服务&#xff0c;可以作为域名和IP地址相互映射的一个分布式数据库&#xff0c;提供域名与IP地址的解析服务&#xff0c;能够使人们更加方便的使用域名访问互联网而不是记住…

Vue3-11- 【v-for】循环数组

v-for的基本介绍 v-for 是一个指令&#xff0c; 它是用来在 html 模板中实现循环的。它可以循环 普通的数组、也可以直接循环一个范围值&#xff0c;也可以循环对象的每个属性。v-for 的语法介绍 <div v-for"(item,index) in arrayName" : key"index"…

有哪些好用的运维管理软件?哪个工单管理系统的操作简单一些?

运维管理软件可以帮助企业更有效地管理公司内外的事务&#xff0c;比如现在不少公司就引入了工单管理系统来处理后勤和售后的事务。那么&#xff0c;有哪些好用的运维管理软件&#xff1f;哪个的操作简单一些呢&#xff1f;   随着技术的发展和成熟&#xff0c;现在的工单管理…

vue_域名部署无法访问后端

前言 目前部署的比较另类&#xff0c;因为服务器为windows&#xff0c;目前还不是很会nginx&#xff0c;所以现在就只能在服务器上安装nodejs&#xff0c;然后直接使用npm run dev命令行的方式运行项目 遇到的坑 使用ip访问前端的时候&#xff0c;就可以访问&#xff0c;但是…

压缩照片怎么压缩?半分钟解决!

有时候我们在平台上传照片的时候&#xff0c;会有图片大小限制&#xff0c;想要将照片压缩到限制的大小范围内&#xff0c;可以使用专业的图片压缩软件、图片处理软件或者在线网站压缩&#xff0c;下面给大家分享三个方法&#xff0c;压缩照片的同时还能保持图片清晰度哦&#…

Windows10安装Node.js环境

Windows10安装Node.js环境 文章目录 1.下载安装包2.安装配置2.1安装2.2 配置全局的安装路径和缓存路径2.3配置环境变量2.4配置镜像源2.5包管理工具 3.查看版本4.编译跑项目5.总结 1.下载安装包 官方下载网址如下&#xff1a; https://nodejs.org/enInstaller表示是安装程序&a…

【回眸】Tessy 单元测试软件使用指南(三)怎么打桩和指针测试

目录 前言 Tessy 如何进行打桩操作 普通桩 高级桩 手写桩 Tessy单元测试之指针相关测试注意事项 有类型的指针&#xff08;非函数指针&#xff09;&#xff1a; 有类型的函数指针&#xff1a; void 类型的指针&#xff1a; 结语 前言 进行单元测试之后&#xff0c;但凡…

LED透镜粘接UV胶是一种特殊的UV固化胶

LED透镜粘接UV胶是一种特殊的UV固化胶&#xff0c;用于固定和粘合LED透镜。 它具有以下特点&#xff1a; 1. 高透明度&#xff1a;LED透镜粘接UV胶具有高透明度&#xff0c;可以确保光线的透过性&#xff0c;不影响LED的亮度和效果。 2. 快速固化&#xff1a;经过UV紫外线照射…

HTTP 404错误:页面未找到,如何解决

在互联网上浏览时&#xff0c;偶尔会遇到“HTTP 404错误&#xff1a;页面未找到”的提示。这通常意味着用户尝试访问的网页不存在或无法找到。本文将探讨HTTP 404错误的原因以及如何解决这个问题。 一、HTTP 404错误的原因 HTTP 404错误可能是由多种原因引起的。以下是一些常…

SQL进阶理论篇(五):什么是Hash索引

文章目录 简介MySQL中的Hash索引与B树的区别总结参考文献 简介 hash&#xff0c;即哈希&#xff0c;也被称为是散列函数。 Hash在数据库中的应用&#xff0c;可以帮助我们大幅度提升检索数据的效率。 大名鼎鼎的MD5其实就是Hash函数的一种变体。 Hash算法&#xff0c;是通过…

富文本 unpkg.com地址无法访问可替代方案

一、背景&#xff1a; 项目中使用的 wangEditor富文本&#xff0c;使用的地址为&#xff1a;https://unpkg.com &#xff0c;但无法访问&#xff0c;富文本加载不出来&#xff0c;一直转圈圈 二、解决方案&#xff1a; 2.1、方案一 &#xff1a; 将项目中 https://unpkg.co…

k8s容器部署mysql5.7全流程分享

文章目录 一、前言二、打开dockerhub 看到mysql的版本为 5.7三、K8S 容器编排3.1、编写POD的相关信息3.2、编写mysql的data存储位置3.3、编写mysql的my.cnf的挂载文件3.4、编写mysql的service端口 四、启动并禁用root账户4.1 登录&#xff0c;默认密码1234564.2 配置账户权限 五…

Linux系统编程(二):标准 I/O 库(下)

参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 标准 I/O 库简介 标准 I/O 库是指&#xff1a;标准 C 库中用于文件 I/O 操作&#xff08;如&#xff1a;读、写文件等&#xff09;相关的一系列库函数的集合 标准 I/O 库函数相关的函数定义都在头文件 &…

【MySQL学习之基础篇】概述

文章目录 1. mysql的启动和停止命令2. 客户端连接3. 数据模型 1. mysql的启动和停止命令 通过指令启动或停止&#xff0c;以管理员身份运行cmd&#xff0c;进入命令行执行如下指令&#xff1a; &#xff08;1&#xff09;启动myaql net start mysql&#xff08;2&#xff09;…

PyTorch: 基于【VGG16】处理MNIST数据集的图像分类任务【准确率98.9%+】

目录 引言在Conda虚拟环境下安装pytorch步骤一&#xff1a;利用代码自动下载mnist数据集步骤二&#xff1a;搭建基于VGG16的图像分类模型步骤三&#xff1a;训练模型步骤四&#xff1a;测试模型运行结果后续模型的优化和改进建议完整代码结束语 引言 在本博客中&#xff0c;小…

商城后台管理系统--->新闻简报(富文本编辑器,文章,图片上传)

在商城的项目里面需要添加新闻&#xff0c;使用富文本编辑器&#xff0c;我用的是 wangEditor这个编辑器挺好用的&#xff0c;而且也方便简单&#xff0c;官网也是中文的wangEditor 这是做的添加新闻的页面 我用的是SCUI框架,引入的是npm,具体可看官网 npm install wangedit…

【Docker实战】基于Dockerfile搭建LNMP+wordpress

一、项目背景和要求 公司在实际的生产环境中&#xff0c;需要使用Docker 技术在一台主机上创建LNMP服务并运行Wordpress网站平台。 然后对此服务进行相关的性能调优和管理工作 二、架构&#xff1a; nginx172.111.0.10docker-nginxmysql172.111.0.20docker-mysqlPHP172.111…

map 和 multimap 存储区别 、取消自动排序 unordered_map

测试代码 std::map<int, CString > Map1;Map1.insert({ 6, L"HN400*200*11*8" });Map1.insert({ 5, L"HN200*200*11*8" });Map1.insert({ 7, L"HN100*200*11*8" });Map1.insert({ 4, L"HN200*200*11*8" });Map1.insert({ 4, L…