前言
我们学习类与对象,认识了如下的类的6个默认成员函数:
而实现一个日期类便可以覆盖构造函数、析构函数、拷贝构造函数、赋值运算符的重载四个函数的知识点。
我们今天的博客便用c++实现日期类,盘点构造函数和析构函数的知识点和重难点。
📜构造函数
我们在之前在用C语言学习链表、栈、队列时,需要调用一个函数Init() 对数据进行初始化,而每一次都调用函数进行初始化,未免有些太过麻烦。
于是,在c++中,我们便产生了构造函数,即编译器自行生成了一个具有初始化功能的默认成员函数,在对象创建时,便实现了初始化。
💡注意:构造函数是特殊的成员函数,虽然名称叫做构造,但构造函数的主要任务并不是开空间创建对象,而是初始化对象。
//无参构造函数
Date()
{
}
Date(int year, int month, int day)//带参构造函数
{
_year = year;
_month = month;
_day = day;
}
void TestDate()
{
Date d1;//调用无参构造函数
Date d2(2023,10,31);//调用带参的构造函数
}
📅构造函数的特性
🔔 函数名和类名相同
🔔无返回值
🔔 对象实例化时编译器自动调用对应的构造函数
🔔构造函数可以重载
🔔 如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义,编译器将不再生成。
class Date
{
//显式定义的构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int main()
{
Date d3;
d3.print();
}
这里我们将程序进行运行,来看看会出现什么结果?
这里出现了报错。原因是:Date类中我们自行定义了构造函数,代码的编译失败,显式定义构造函数,编译器便不会自行生成默认的构造函数,而我们在进行对象的创建时,并没有传递参数,所以这里便看到了报错。
而如果把我们显式定义的构造函数屏蔽掉,编译器便不会报错,因为此时编译器生成了一个无参的默认构造函数。
#include<iostream>
class Date
{
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
这时,老铁们可能会有一些疑问:对象调用编译器生成的默认构造函数,但d3中的对象_year,_month,_day均是随机值,那默认成员函数有什么用呢?
c++把类型分成了内置类型(基本类型)和自定义类型。自定义类型就是我们使用的class/struct/union等自己定义的类型。所以,编译器在生成默认的构造函数时会对自定义类型成员调用它的默认函数,而为了弥补c++对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明可以给默认值。
所以,如果自定义类型中全部都是自定义类型,那么我们可以考虑让编译器自己自动调用每个自定义类型所对应的构造函数。
📜析构函数
📅概念
通过前面构造函数的学习,我们知道了一个对象是怎么来的,那么一个对象又是怎么没的呢?
是析构函数的功劳。
析构函数:与构造函数的功能相反,相当于Destory(),但析构函数不是完成对对象本身的销毁,局部对象的销毁工作是由编译器来完成的。而对象在销毁时自动调用析构函数,完成对象中资源的清理工作。
📅特性
析构函数是特殊的成员函数,其特征如下:
🔔析构函数名是在类名前面加上一个字符~
🔔无参数无返回值类型
🔔一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
🔔对象生命周期结束时,c++编译系统会自动调用析构函数。
那么通俗来说,什么时候需要调用析构函数呢?即开辟了一块新的空间时,所以,日期类的实现本质上是不需要析构函数的。
在我们学习stack时,我们了解到stack需要开辟空间存放数据,那么在函数结束时,我们便需要进入析构函数进行数据的清理。
class stack
{
public:
stack(int capacity = 10)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == NULL)
{
perror("malloc fail");
}
_size = 0;
_capacity = 0;
}
void Push(int data)
{
_a[_size] = data;
_size++;
}
~stack()
{
if (_a)
{
free(_a);
_a = NULL;
_size = 0;
_capacity = 0;
}
}
private:
int* _a;
int _size;
int _capacity;
};
🔔关于编译器自动生成的函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成默认析构函数,对自定义类型则调用它的析构函数。
下面,我们来看一看编译器对于自定义类型是如何调用它的析构函数的。
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
private:
//内置类型
int _year = 2023;
int _month = 11;
int _day = 7;
//自定义类型
Time _t;
};
💡这里我们发现,类中定义自定义类型,在函数结束时,会自动调用该自定义类型的析构函数。
所以,虽然在main中没有直接创建Time类的对象,但是最后还是会调用Time类的析构函数,这时为什么呢?
原因是main内创建了Date对象d,而d中包含着四个成员变量,其中_year,_month,_day三个是内置类型,没有开辟新的空间,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的_t对象销毁,所以要调用Time类的析构函数。但是main函数中不能直接调用析构函数,所以编译器会调用Date类的析构函数,而Date没有显式提供给我们,于是变异去默认生成了一个Date的析构函数,目的是在其内部调用Time类的析构函数。即当Date对象销毁时,要保证其内部每个自定义的对象都可以正确销毁。
📜总结
今天的内容到这里就已经全部结束了,我们学习了构造函数和析构函数的概念与特性,加以例子来辅助理解,老铁们可以将代码自行拿走自己运行一下,加深其理解~~
🎁今天的内容就分享到这里,欢迎各位老铁们订阅专栏🎓《c++初阶》 ,后续会持续更新!
🎁今天的内容就分享到这里,博主还是新手小白,希望大佬们可以多多批评指正,创作不易,希望得到您的支持和喜爱!感谢您的陪伴,草莓base将会努力将更多优质内容分享给大家!欢迎各位老铁在评论区讨论!!