C++初阶——类和对象(三) 构造函数、析构函数
C++初阶——类和对象(三)上期内容,我们围绕类对象模子的巨细盘算,成员存储方式,this指针,以及C++实现栈和C语言的比较,进一步认识了C++的封装特性。本期内容,我们开始先容类的默认成员函数,这是类和对象板块中最为紧张,也是相对复杂的内容,让我们一起探索!
弁言
假如一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数(默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数):
[*]初始化和清算:构造函数、析构函数
[*]拷贝复制:拷贝构造函数、赋值重载函数
[*]取地点重载:普通对象取地点重载和const对象取地点重载函数
一、构造函数
1.配景引入
https://i-blog.csdnimg.cn/direct/b10d694ce8914dc0bb6385660949ba55.png
https://i-blog.csdnimg.cn/direct/e3bd54b77f0c44e99640d2d264a25c18.png
我们再来回顾一下,_year,_month,_day是三个私有的成员变量,Init和Print是外界可以直接访问的成员函数,在main函数中,我们先要通过类来示例化出一个对象,比如这里的Date d1; Date d2; Date d3;等等,然后调用类里面的成员函数。这里基于同一个类创建了许多对象,然而在成员函数中似乎并没有通报代表各个对象的形参,那么编译器是怎么区分的呢?很简单,由于this指针的存在。我们以d1.Init(2025,3,10);为例,this指针指向当前的对象——d1,然后在调用函数Init时,隐式通报了这个指针,然后通过这个指针访问d1中的成员变量_year、_month、_day,将它们分别赋值为2025、3、10,如图所示:
https://i-blog.csdnimg.cn/direct/8cb9e28a5f164c4ebaa7b74c3f34db01.png
当运行到下一个对象d2时,this指针指向的就是d2了。
进一步探究
假如我们细致观察,不难发现这里分为了两步走:第一步,先通过类实例化一个对象,也就是Date d1;,然后再用详细的数值将其初始化d1.Init(2025,3,10);,其实是有些贫困的,我们能不能在实例化对象的同时,就将信息设置进去呢?——答案是肯定的,C++本身就是对C语言的优化,构造函数应运而生!
2.什么是构造函数?
构造函数是一个特殊的成员函数,名字与类名相同。创建类对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
https://i-blog.csdnimg.cn/direct/0391658aaea0454aaa7ee86aad021584.png
https://i-blog.csdnimg.cn/direct/f3b4325d97644fbdb45da29d0286266c.png
在这里,我们已经将原来的初始化函数改造成了一个构造函数,构造函数的函数名与类名相同,没有返回值(这里的没有返回值不是说返回一个void,写成void Date(int year, int month, int day),而是什么都不要写,直接就是Date(int year, int month, int day)),至于为什么,这是语法规定,这是一个特殊的函数,有特殊的待遇,只有这样写,编译器才知道这是构造函数。对于构造函数的调用,也是很有趣的,我们在实例化对象的同时,反面跟上了一个括号,里面就是想要在对象中填入的信息Date d1(2025, 3, 10);,这样一来,我们不但实例化了一个对象,还填入了我们想要的信息,一步办理,非常方便。之前的那种两步走,是先根据图纸建房子,然后里面的家具还要本身来置办;现在是把装修也一并让别人做好了,本身直接拎包入住,确实方便的多。
3.构造函数的特性
构造函数是特殊的成员函数,需要注意的是,构造函数固然名称叫构造,但是构造函数的重要任务并不是开空间创建对象,而是初始化对象。也很好理解,之前是两步走,现在是一步走,至于减少的那一步——填入想要的信息,就是构造函数的功劳。
(1)构造函数的重载
构造函数是可以重载的,根据传参的差异,调用对应的构造函数,如图所示:
https://i-blog.csdnimg.cn/direct/54ce1c3a19914aa38846bad440425eeb.png
这里就写了两个构造函数,此中一个是无参数的,这些都可以理解,但是,我们注意到,为什么在调用的时候写的是Date d1;而不是Date d1();呢?我们先来看一下结果:
https://i-blog.csdnimg.cn/direct/f86a7ff3e8894722b29a616a8c92e564.png
这里什么都没有输出,也就是没有调用谁人构造函数,这是为什么呢?细致观察我们发现,Date d1();像是函数的声明,Date是返回值的范例,d1是函数名,()表现这个函数不需要传参。基于这种情况,编译器不知道这里到底是在类实例化对象还是在声明一个名为d1的函数。因此,我们对于无参的构造函数,在实例化对象时就不需要加()了。
当然,还有一点,一开始的构造函数示例中,是提供了缺省参数的,而且是全缺省,这里做出了改变。假如依然是全缺省会发生什么呢?如图所示:
https://i-blog.csdnimg.cn/direct/afb837d14385473a88b2c95b13348a90.png
https://i-blog.csdnimg.cn/direct/f5685a6175034318b47d3cd439dc6780.png
还是一个调用不明确的题目:不传参,不但无参的函数可以调用,全缺省的也可以调用,由于它可以自动帮你把数据补上。在C++入门中已经讲过这一点,这里就当是复习一下吧。
(2)默认构造函数
默认构造函数的分类
默认构造函数是无需参数即可调用的构造函数,我们既可以显式声明,也可以让编译器隐式生成。无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数,再简单一点,不需要传参就可以调用的构造函数都是默认构造函数:
[*]无参构造函数
https://i-blog.csdnimg.cn/direct/bd7f8169499f4c8ab1ca54f5dd6bc684.png
[*]全缺省构造函数
https://i-blog.csdnimg.cn/direct/65e12caf093b46e1ac04afdff6afc032.png
[*]编译器默认生成的构造函数
https://i-blog.csdnimg.cn/direct/96f50aad623047cc960d69b1e25b0839.png
编译器自动生成的构造函数
假如类中没有显式界说构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式界说编译器将不再生成。注意,这里是无参的,也就是说调用的时候直接写Date d1;即可,如图所示:
https://i-blog.csdnimg.cn/direct/90af294002e548778571a55eaf94eb2e.png
https://i-blog.csdnimg.cn/direct/80af65bb4fae49c495b949e425c24775.png#pic_center
这里的默认构造函数似乎没什么用,对于这些变量的值,好像也没有进行处理,仍旧是随机值,那么默认构造函数还有它存在的价值吗?
其实是这样的:C++把范例分成内置范例(基本范例)和自界说范例。内置范例就是语言提供的数据范例,如:int、char、double……,自界说范例就是需要本身界说的,如struct、class……
编译器生成的默认构造函数对内置范例不作处理,对自界说范例则是调用它的默认构造。因此,一般情况下,有内置范例成员,就需要本身写构造函数,不能用编译器本身生成的;全部都是自界说范例成员,可以思量让编译器本身生成。
我们来举个自界说范例的例子:
https://i-blog.csdnimg.cn/direct/5f1117aadab349d38f1ec78bb84cc6ae.png
在Date类的成员变量中,不但有内置范例,还有自界说范例变量Time _t;那么在实例化对象时,内置范例不作处理,自界说范例调用它的默认构造,而Time类的默认构造我们已经本身界说了,因此会打印出对应的结果,我们也可以调试看一看:
https://i-blog.csdnimg.cn/direct/3db5fefa7e09460f914eb94cbd4b3d1e.png
由于Date类中没有本身写构造函数,内置范例都是随机值,而自界说范例Time _t就调用了它的默认构造,而Time的默认构造是我们本身实现的,对内置范例进行了初始化。
[*]C++11 中针对内置范例成员不初始化的缺陷,又打了补丁,即:内置范例成员变量在类中声明时可以给默认值:
https://i-blog.csdnimg.cn/direct/9bced89f4954483998be6b666638d7b1.png
这里没有本身写构造函数,默认值给在成员变量的声明里。
二、析构函数
析构函数是特殊的成员函数,其特征如下:
[*]析构函数名是在类名前加上字符 ~。
[*]无参数无返回值范例。
[*]一个类只能有一个析构函数。若未显式界说,系统会自动生成默认的析构函数。注意:析构函数不能重载
[*]对象生命周期结束时,C++编译系统系统自动调用析构函数。
1.本身界说的析构函数
我们以一个栈为例,由于栈中涉及到动态内存开发,不使用了需要及时销毁开发的内存空间,否则会造成内存泄漏,有了析构函数,我们只需要在类中界说一下,在类外它就会自动调用了,代码如下:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
cout << "~Stack" << endl;
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack st1;
return 0;
}
运行结果如图:
https://i-blog.csdnimg.cn/direct/ae87a214025545b28e4f400f7bb3dfff.png
2.编译器自动生成的析构函数
关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定范例成员调用它的析构函数。
https://i-blog.csdnimg.cn/direct/6868cd962b3b43d7b699ee29e4d2883a.png
假如类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,肯定要写,否则会造成资源泄漏,比如Stack类。
本期总结+下期预报
本期内容非常紧张,详细先容了类的构造函数和析构函数,下期将继承讲授拷贝构造函数等相关内容!
感谢大家的关注,我们下期再见!
https://i-blog.csdnimg.cn/direct/f1e86197457e4e1f92195103aaea6326.png#pic_center
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]