C++:类的默认成员函数------构造函数析构函数(超详细解析,小白一看就懂!)

目录

一、前言

二、为什么会出现构造函数和析构函数 

 三、构造函数

🍎构造函数的概念 

🍐构造函数特性 

💦解释特性3:对象实例化时编译器自动调用对应的构造函数

💦解释特性4:构造函数支持重载

 💦解释特性5:如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

 💦解释特性6:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

四、析构函数 

🍉析构函数概念 

🍓析构函数特性 

 五、共勉


一、前言

        在我们前面学习的中,我们会定义成员变量成员函数,这些我们自己定义的函数都是普通的成员函数,但是如若我们定义的类里什么也没有呢?是真的里面啥也没吗?如下:

class Date {};

如果一个中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的任何一个类在我们不写的情况下,都会自动生成6个默认成员函数。

【默认成员函数概念】:用户没有显式实现,编译器会生成的成员函数称为默认成员函数


 


        其中两个默认成员函数是用来初始化清理的分别为:构造函数、析构函数

      本次博客将详解为什么会出现这两个函数,这两个函数将如何使用 的问题

二、为什么会出现构造函数和析构函数 

 首先看下面这段 C语言代码

typedef struct Date
{int year;int month;int day;
}D;void Init(D* date)
{date->year = 2023;date->month = 10;date->day = 21;
}
void Printf(D* date)
{cout << date->year << "-" << date->month << "-" << date->day << endl << endl;
}
void Destory(D* date)
{date->year = 0;date->month = 0;date->day = 0;
}
int main()
{D date;Init(&date);Printf(&date);Destory(&date);return 0;
}


 

⚠ 注意:大家在日常写代码和刷题的时候,肯定会有过忘记初始化,或者忘记销毁,这些小细节很容易被大家忽略,但是出现在代码中,就会出现报错,导致我们写代码的时候,就很烦。

⚠ 忘记写初始化:输出随机值,结果会错误
 


⚠ 忘记写销毁:时间久了便会造成【内存泄漏】
 



💦你是否发现若是我们要去使用一个Date的话,通常不会忘了去往里面入数据或者是出数据,但是却时常会忘了【初始化】和【销毁】。这要如何是好呢😔
 

🔑 解决方案:

1️⃣:在上一文的学习中,我们学习到了一个类中的一个东西叫做this指针,只要是在成员函数内部都可以进行调用。而且还知晓了C++中原来是使用this指针接受调用对象地址的机制来减少对象地址的传入,减轻了调用者的工作。这也是C++区别于C很大的一点

2️⃣:那C++中是否还有东西能够替代【初始化】和【销毁】这两个工作呢?答案是有的,就是我们接下来要学习的构造函数】和【析构函数
 

 三、构造函数

🍎构造函数的概念 

 如下的日期类:

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "今日日期输出:" << endl;cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Init(2023, 10, 21);d1.Print();return 0;
}

运行效果:


        正常情况下,我们写的这个日期类,首先初始化,其次打印。但如果说你突然忘记初始化了,直接就开始访问会怎么样呢?



        从运行结果上看,没初始化直接访问输出的是随机值。 忘记初始化其实是一件很正常的事情,C++大佬在这一方面为了填补C语言的坑(必须得手动初始化)。因而就设计出了构造函数。
        构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。其目的就是为了方便我们不需要再初始化。

🍐构造函数特性 

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

  1. 函数名和类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
  6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

如下即为构造函数:

Date()
{_year = 1;_month = 1;_day = 1;
}

 💦解释特性3:对象实例化时编译器自动调用对应的构造函数

        也就是说我们在实例化一个对象后,它会自动调用这个构造函数,自动就初始化了,我们可以通过调试看看:
 

💦解释特性4:构造函数支持重载

如下的函数:

Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}

        像这个重载函数是明确了我们要传参的,所以我们在实例化对象后就必须把参数写上去(虽然看着奇奇怪怪,但是没有办法,毕竟我们普通的调用,参数都是在函数名后面,而这个参数在实例化对象后面):

Date d2(2023, 10, 21);

来输出和我们先前的构造函数对比看看:

  • 注意:没有参数时我在调用的时候不能加上括号(),切忌!!构造函数尤为特殊
  • 如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

无参的情况下必须要像我们刚开始实例化的d1那样:

Date d1;
d1.Print();
  •  构造函数的重载我们推荐写成全缺省的样子:
//普通的构造函数Date(){_year = 1;_month = 1;_day = 1;}
//全缺省的构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}

        首先,普通的构造函数和全缺省的构造函数在不调用的情况下可以同时存在,编译也没有错误。但是在实际调用的过程中,会存在歧义。如下的调用:

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

        此时我实例化的d1到底是调用普通的构造函数?还是调用全缺省的构造函数?并且此段代码编译出现错误。何况我在没有调用函数的情况下编译是没错的。

       🔑 由此可见:它们俩在语法上可以同时存在,但是使用上不能同时存在,因为会存在调用的歧义,不知道调用的是谁,所以一般情况下,我们更推荐直接写个全缺省版的构造函数,因为是否传参数可由你决定。传参数数量也是由你决定。

 💦解释特性5:如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

 看如下代码,自己不去写构造函数,使用编译器默认的构造函数:

class Date
{
public:// 我们不写,编译器会生成一个默认无参构造函数/*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 d;d.Print();
}


        不是说好我不自己写构造函数,编译器会默认生成吗?为什么到这又是随机值了?这难道也算初始化?别急,搞清楚这个得先明白默认构造函数
⚠ 默认构造函数:

  • 1. 我们不写编译器默认生成的那个构造函数,叫默认构造
  • 2. 无参构造函数也可以叫默认函数
  • 3. 全缺省也可以叫默认构造

      总结: 可以不传参数就调用构造,都可以叫默认构造

C++把变量分成两种:

      1️⃣:内置类型/基本类型:int、char、double、指针……
      2️⃣:自定义类型:class、struct去定义的类型对象

        C++默认生成的构造函数对于内置类型成员变量不做处理,对于自定义类型的成员变量才会处理,这也就能很好的说明了为什么刚才没有对年月日进行处理(初始化),因为它们是内置类型(int类型的变量)


让我们来看看自定义类型是如何处理的。

class A
{
public:A(){cout << "A()" << endl;_a = 1;}
private:int _a;
};

        首先,这是一个名为A的类,有成员变量_a,并且还有一个无参的构造函数,对_a初始化为1。接着:

class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;cout << endl;}
private:int _year = 2023;int _month = 11;int _day = 11;A _aa;
};int main()
{Date d1;d1.Print();return 0;
}

       通过运行结果以及调试,也正验证了默认构造函数对自定义类型才会处理。这也就告诉我们,当出现内置类型时,就需要我们自己写构造函数了。

什么时候使用默认构造函数会凸显出其价值呢?就比如我们之前写的括号匹配这道题:

class Stack
{
public:Stack(){_a = nullptr;_top = _capacity;}
private:int* _a;int _top;int _capacity;};class MyQueue 
{
public://默认生成的构造函数就可以用了void push(int x){}int pop() {}
private:Stack _S1;Stack _s2;
};

        此时我队列里自定义类型_s1和_s2就不需要单独写初始化了,直接用默认的。但是如果栈里没有写构造函数,那么其输出的还是随机的,因为栈里的也是内置类型。就是一层套一层,下一层生效的前提是上一层地基打稳了。

🔑总结:

  1. 如果一个类中的成员全是自定义类型,我们就可以用默认生成的函数
  2. 如果有内置类型的成员,或者需要显示传参初始化,那么都要自己实现构造函数。

 💦解释特性6:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个

🔑 默认构造函数:

  • 1. 我们不写编译器默认生成的那个构造函数,叫默认构造
  • 2. 无参构造函数也可以叫默认函数
  • 3. 全缺省也可以叫默认构造

总结: 可以不传参数就调用构造,都可以叫默认构造


🔑既然我默认构造函数只对自定义类型才会处理,那如果我不想自己再写构造函数也要对内置类型处理呢?我们可以这样做:

class Date
{
public:// 我们不写,编译器会生成一个默认无参构造函数/*Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:// C++11 打的补丁,针对编译器自己默认成员函数不初始化问题int _year = 2023;int _month = 10;int _day = 21;
};
int main()
{// 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数Date d;d.Print();
}

 🔑总结:

  1. 构造函数是类中默认就带有的,不过日常自己在写一个类的时候尽量不要用默认生成的,最好是自己写一个,无参或者是缺省的都可以,但是不可以无参和全缺省共存,会引发歧义。
  2. 若是使用默认生成的构造函数,会引发一些语言本身就带有的缺陷,【内置类型】的数据不会被初始化,还会是一个随机值;【自定义类型】的数据会调用默认构造函数(默认生成、无参、全缺省),若是不想看到随机值的话,可以参照C++11中的特性,在内置类型声明的时候就为其设置一个初始化值,便不会造成随机值的问题

四、析构函数 

🍉析构函数概念 

        前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?
        析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
 

🍓析构函数特性 

析构函数是特殊的成员函数。

其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
  5. 编译器生成的默认析构函数,对会自定类型成员调用它的析构函数

我们实际写一个析构函数看看:

~Date()
{cout << "~Date()" << endl;
}

带入示例再看看:

class Date
{
public:Date(int year = 2023, int month = 10, int day = 21){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}~Date(){cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();return 0;
}

        首先,我实例化出的d1会调用它的默认构造函数进行初始化,其次,出了作用域后又调用其析构函数,这也就是为什么输出结果会是~Date()
        析构的目的是为了完成资源清理,什么样的才能算是资源清理呢?像我这里定义的年月日变量就不需要资源清理,因为出了函数栈帧就销毁,真正需要清理的是malloc、new、fopen这些的,就比如清理栈里malloc出的

class Stack
{
public://构造函数Stack(int capacity = 10){_a = (int*)malloc(sizeof(int) * capacity);assert(_a);_top = 0;_capacity = capacity;}//析构函数~Stack(){free(_a);_a = nullptr;_top = _capacity = 0;}
private:int* _a;int _top;int _capacity;
};
int main()
{Stack st;
}

这里不难感慨C++的构造函数就像先前C语言常写的Init,而析构函数就像Destroy

  • 看如下的题目:现在我用类实例化出st1和st2两个对象,首先,st1肯定先构造,st2肯定后构造,这点毋庸置疑,那关键是谁先析构呢?
int main()
{Stack st1;Stack st2;
}

答案:st2先析构,st1后析构

解析:这里st1和st2是在栈上的,建立栈帧,其性质和之前一样,后进先出,st2后压栈,那么它肯定是最先析构的。所以栈里面定义对象,析构顺序和构造顺序是反的。

        若自己没有定义析构函数,虽说系统会自动生成默认析构函数,不过也是有要求的,和构造函数一样,内置类型不处理,自定义类型会去调用它的析构函数,如下:

class Stack
{
public://构造函数Stack(int capacity = 10){_a = (int*)malloc(sizeof(int) * capacity);assert(_a);_top = 0;_capacity = capacity;}//析构函数~Stack(){cout << "~Stack():" << this << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:int* _a;int _top;int _capacity;};
class MyQueue
{
public://默认生成的构造函数可以用//默认生成的析构函数也可以用void push(int x){}int pop(){}
private:Stack _S1;Stack _s2;
};
int main()
{MyQueue q;
}


        对于MyQueue而言,我们不需要写它的默认构造函数,因为编译器对于自定义类型成员(_S1和_S2)会去调用它的默认构造,Stack提供了默认构造,出了作用域,编译器会针对自定义类型的成员去默认调用它的析构函数,因为有两个自定义成员(_S1和_S2),自然析构函数也调了两次,所以会输出两次Stack()……
 

🔑总结:

  • 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

 五、共勉

        以下就是我对C++类的默认成员函数--------构造函数&&析构函数的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对C++ 类的默认成员函数-------拷贝构造&&赋值重载的理解,请持续关注我哦!!!     

 

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

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

相关文章

平板有必要买触控笔吗?推荐的ipad手写笔

iPad之所以能吸引这么多人&#xff0c;主要是因为它的功能出色。用来画画、做笔记&#xff0c;也是一种不错的体验。但如果只是用来看电视和打游戏的话&#xff0c;那就真的有点大材小用了。如果你不需要昂贵的苹果电容笔&#xff0c;也不需要用来专业的绘图&#xff0c;那你可…

经典题型---旋转数组

经典题型—旋转数组 文章目录 经典题型---旋转数组一、题目二、代码实现 一、题目 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步…

2021年03月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 下列代码的输出结果是&#xff1f;&#xff08; &#xff09; x 0x10print(x)A&#xff1a;2 B&#xff1a;8 C&#xff…

读《Gaitset: Regarding gait as a set for cross-view gait recognition》

2019在AAAI&#xff08;还有一版叫GaitSet: Regarding Gait as a Set for Cross-View Gait Recognition&#xff0c;大体上一样&#xff09; 摘要 现有的步态识别方法要么利用步态模板&#xff0c;难以保存时间信息&#xff0c;要么利用保持不必要的顺序约束的步态序列&#x…

免费Scrum管理工具-Leangoo领歌

Leangoo领歌是一款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 
 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速…

MySQL -- 环境安装(CentOS7)

MySQL – 环境安装&#xff08;CentOS7&#xff09; 文章目录 MySQL -- 环境安装&#xff08;CentOS7&#xff09;一、环境安装1.卸载不必要的环境2.检查系统安装包3.卸载默认安装包4.获取MySQL官方yum源6.看看yum源能不能正常工作7.安装mysql服务 二、MySQL登录与配置1.启动My…

项目经理之识别项目干系人

项目干系人管理是项目管理中的重要一环&#xff0c;识别和管理好项目干系人是成功实施项目的关键之一。本文将介绍4321项目干系人识别方法、干系人等级册以及五步判断法等工具&#xff0c;帮助项目经理更好地识别和管理项目干系人。同时&#xff0c;本文还将介绍干系人能量方格…

智慧矿山矿山安全生产:皮带撕裂识别AI算法不用激光,能迅速识别皮带纵撕!

近些年来&#xff0c;智慧矿山在煤矿行业中发挥着越来越重要的作用。皮带的功能对于矿山运营至关重要&#xff0c;而皮带撕裂是造成生产中断、人身伤害等问题的重要原因之一。为了准确、及时地检测皮带撕裂的情况&#xff0c;AI算法的应用成为智慧矿山的关键。 ​​​​​​​…

软件报错msvcr120.dll丢失怎么办?五个有效修复方法分享

msvcr120.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2012 Redistributable Package的一部分。如果你的电脑在运行一些需要这个文件的程序时出现了“msvcr120.dll丢失”的错误&#xff0c;那么就意味着你的电脑缺少了这个文件&#xff0c;或者这个文件已经损坏…

3、Kafka Broker

4.1 Kafka Broker 工作流程 4.1.1 Zookeeper 存储的 Kafka 信息 &#xff08;1&#xff09;启动 Zookeeper 客户端。 [hadoop102 zookeeper-3.5.7]$ bin/zkCli.sh&#xff08;2&#xff09;通过 ls 命令可以查看 kafka 相关信息。 [zk: localhost:2181(CONNECTED) 2] ls /kaf…

Three.js + Tensorflow.js 构建实时人脸点云

本文重点介绍使用 Three.js 和 Tensorflow.js 实现实时人脸网格点云所需的步骤。 它假设你之前了解异步 javascript 和 Three.js 基础知识&#xff0c;因此不会涵盖基础知识。 该项目的源代码可以在此 Git 存储库中找到。 在阅读本文时查看该代码将会很有帮助&#xff0c;因为…

【实战】学习 Electron:构建跨平台桌面应用

文章目录 一、Electron 简介二、Electron 的优势1. 学习曲线平缓2. 丰富的生态系统3. 跨平台支持4. 开源和社区支持 三、Electron 的使用1. 安装 Node.js2. 安装 Electron3. 创建项目4. 初始化项目5. 安装依赖6. 创建主进程文件7. 创建渲染进程文件8. 打包应用程序9. 运行应用程…

在 Ubuntu 22.04安装配置 Ansible

一、按官网指引安装 我使用的ubuntu22.04版本&#xff0c;使用apt安装。官网指引如下&#xff1a; $ sudo apt-get install software-properties-common $ sudo apt-add-repository ppa:ansible/ansible $ sudo apt-get update $ sudo apt-get install ansible 由于内部网络…

36 机器学习(四):异常值检测|线性回归|逻辑回归|聚类算法|集成学习

文章目录 异常值检测箱线图z-score 保存模型 与 使用模型回归的性能评估线性回归正规方程的线性回归梯度下降的线性回归原理介绍L1 和 L2 正则化的介绍api介绍------LinearRegressionapi介绍------SGDRegressor 岭回归 和 Lasso 回归 逻辑回归基本使用原理介绍正向原理介绍损失…

Elasticsearch集群搭建与相关知识点整理

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章参考网上的课程&#xff0c;介绍Elasticsearch集群的搭建&#xff0c;以及Elasticsearch集群相关知识点整理。 如果文章有什么需要改进的地方还请大佬不吝赐教&am…

Git Cherry Pick的使用

cherry-pick命令的基本用法 cherry-pick命令的基本语法如下&#xff1a; git cherry-pick <commit>其中&#xff0c;<commit>是要应用的提交的哈希值或分支名。该命令会将指定的提交应用到当前分支上&#xff0c;并创建一个新的提交。 使用场景 cherry-pick命令…

【JavaEE】JUC 常见的类 -- 多线程篇(8)

JUC 常见的类 1. Callable 接口2. ReentrantLock3. 原子类4. 线程池5. 信号量 Semaphore6. CountDownLatch 1. Callable 接口 Callable Interface 也是一种创建线程的方式 Runnable 能表示一个任务 (run方法) – 返回 voidCallable 也能表示一个任务(call方法) 返回一个具体的…

ArcGIS笔记11_提取栅格中的数据到点要素

本文目录 前言Step 1 准备好点要素和栅格文件Step 2 多值提取到点 前言 很多时候需要将栅格中的数据提取到点要素&#xff0c;让点获取到栅格文件对应坐标所包含的数据&#xff0c;本博文主要介绍这个操作。 Step 1 准备好点要素和栅格文件 如下图所示&#xff1a; Step 2 多…

基于MATLAB的图像条形码识别系统(matlab毕毕业设计2)

摘要 &#xff1a; 本论文旨在介绍一种基于MATLAB的图像条形码识别系统。该系统利用计算机视觉技术和图像处理算法&#xff0c;实现对不同类型的条形码进行准确识别。本文将详细介绍系统学习的流程&#xff0c;并提供详细教案&#xff0c;以帮助读者理解和实施该系统。 引言…

02HTML功能元素

1.功能元素 1.1.列表标签 ​ 列表标签的作用: 给一堆数据添加列表语义, 也就是告诉搜索引擎告诉浏览器这一堆数据是一个整体 - HTML中列表标签的分类 ​ 无序列表(最多)(unordered list) ​ 有序列表(最少)(ordered list) ​ 定义列表(其次)(definition list) 1.1.1.无序列…