💖作者:小树苗渴望变成参天大树
❤️🩹作者宣言:认真写好每一篇博客
💨作者gitee:gitee
💞作者专栏:C语言,数据结构初阶,Linux,C++
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!
前言
从这篇开始我们才算是真正的步入类和对象的学习,在C++入门的时候,就已经说过,这是最难学的语言之一,在于它前期的门槛很高,学起来挺吃力的,所以我不打算把类和对象的重点放到一篇来讲,怕大家坚持不下去看完,所以我把中篇分好几篇来学,这样大家学起来也不会特别的吃力,今天我们就来讲解一下在类和对象的一组默认成员函数,学好着两个,对后面其他的默认函数学起来也有很大的帮助,那让我们开始进入正文吧。
一、类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
二、案例引入
大家还记得我们在学习栈的时候,我写过一篇博客,是关于有效的括号这个题目,这个题目,我们有多处返回值.
大家看到每次返回的时候都要进行一次销毁,所以特别的麻烦,二有时候忘记的初始化,造成程序的崩溃,这题用我们目前学到的C++写和C语言差距不大,等学到后面的STL的时候才会特别的爽,我们先用C++把大致框架写出来:
我们将刚才的函数都封装在一个类中,注意这时候就不需要传地址了,因为类里面的成员变量在成员函数里面都是可以直接使用的。我们看判断函数里面怎么实现的:
大家看到几乎和C语言一样,所以优势不明显,这时候我们需要来讲解一下构造函数和析构函数:
构造函数:构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
对于刚才的题目,我们把初始化函数直接改成构造函数:
这时候我们不需要写调用初始化函数编译器自动调用这个构造函数来实现初始化
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
我们把销毁函数直接写成析构函数:
最主要的是最后的差距就非常明显
通过着两个例子:让大家初步认识了构造函数和析构函数,可以帮你自动完成忘记的事,并且可以完成初始化的数据,对于这个例子的栈我给大家方出来,因为后面徽通过这个栈类来讲解构造和析构其他的知识点
栈类:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
这里面的其他操作函数我就不写了和插入类似,把构造和析构写上去。
在接下来重点讲解构造函数和析构函数
三、构造函数
大家刚才可以说值看到了构造函数的前 两 个特性,名字和类名相同,没有返回值,接下来介绍其他两个特性
我们来看一个日期类:
我们通过这个例子不难出来我们确实是自动调用了构造函数,大家看到了第 三 个特性。
在日常中,我们肯定不止一种初始化方式,这时候就要说到第 四 个特性,构造函数也可以重载。我们就以这个栈类来写一个函数重载的例子:这是一开始数组里面就有数据想要导到栈里面去
我们在日期类中写一个函数重载的构造函数:
对于有参数的需要在创建对象的时候去进行传参,因为他是一个特殊的函数,所以传参也是特殊的,创造者是这样规定的,所以大家不要想和普通函数一样的去调用。因为第一个没有进行初始化所以打印出来的都是随机值
在前面我大致讲了四个特性了,其实它还有要注意的点。
第 五 个特性:
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
我们来看看没有显示调用调用默认生成会发生什么:
按照以往的逻辑应该是把初始化为0,但是这里却没有任何变化,这里也是创造者设计的一个不好的地方,因为是默认生成的所以没办法像之前一样的在构造函数打印一些东西出来看看效果,既然这样,默认构造函数难道真的一点事都不干了吗??我们在来看一下:
大家看看我们加了一个自定义类型, 默认生成的构造函数对于内置类型(int double,char,指针等)不做处理,但是对于自定义类型,会调用它本身的构造类型
所以默认生成的构造函数还是有作用的。
第六个特性:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
我们来看具体例子:
对于语法这两个构造函数是可以具体存在的,但是在调用的时候会出现调用歧义,因为都不需要传参,所以不传参就可以调用的就是默认构造函数
所以这三个只能存在一个。
在C++11的时候就补了这个漏洞,可以在声明的时候个缺省值:
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
这里不是初始化,只是声明,这里给的都是默认值,在没有显示默认构造函数的时候,就会使用这里的缺省值给变量进行赋值,解决了随机值的问题,但之前的问题还保留在,你敢轻易的去修改,怕有人刚好用了这个随机值就麻烦了。但显示了构造函数,就不会使用这些缺省值
那我们什么时候应该写构造函数呢??
- 一般情况下,有内置类型成员,就需要自己写构造函数,有内置类型但有缺省值也可以不用写,看具体条件
- 都是构造函数不用写,他可以调用自己类的构造函数,但是自己类的构造函数还是需要我们自己写的,一切自定义类型追溯到最上面都是内置类型
在两个栈实现队列的时候是典型的不需要写构造函数,因为都是内置类型,刚兴趣的同学可以去力扣上做一下这道题
解决困惑:
==1.==在默认构造函数的时候,说过是不传参的就可以,和传参调用的格式一样,那我们时候这样调用可不可以:
Date d1();//无参
Date d2(20023,5,1)//有参
这样不久更好的理解了,为什么不可以呢?这样容易和函数的声明弄混,而第二个不会弄混
==2.==我们知道在编译器默认生成的构造函数的时候对内置类型不做处理,那为什么加了自定义类型就初始化了呢??
实际上这是编译器自己弄的,不是创造者弄的,在不同的编译器就会出现不同的情况,有vs2013的老铁可以试试去看看结果,本身会不会初始化的,不同的编译器可能有不同的处理方法。
==3.==大家发现调用的时候非常麻烦,有点人会说为什么不能这么调用:
d1.Date();
d1.Date(2023,5,1);
这个方法对我们学习起来很简单,但是这样调用和之前我自己写一个初始化函数调用有啥区别??所以创造者那么做肯定有他的道理
讲完构造函数,接下来讲的析构函数就简单很多,没有什么要特别注意的特性
四、析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
析构函数也是编译器默认生成的,对于内置类型,编译器默认生成的析构函数不做处理,有自定义类型的时候,就会调用自定义类的析构函数,其余没啥要注意的点,因为少了重载而且无参数,就没啥改注意的地方,所以说析构函数还是比较好理解和掌握的
那什么时候不需要写析构函数呢?
- 一般情况下,有动态申请资源就需要显示写析构函数释放资源(栈是典型的例子)
- 没有动态申请就不需要写
- 释放资源的成员都是自定义类型,不需要写析构函数(用两个栈写一个队列)
五、总结
通过这篇学习大家应该了解构造函数和析构函数,他的重要性,和什么时候要写,以及怎么去使用,因为这个对于我们后面学习模拟STL很有帮助,不然后面你根本听不懂,所以这篇博客,大家一定要理解,尤其是默认生成的,他也是有工作要做的,希望大家能够明白,我们这篇知识点就讲到这里了,我们下篇再见