【C++】构造与析构函数
目录:
一、 This指针
(一)使用方法:
二、类的默认成员函数
三、构造函数
(一)构造函数的特点
四、析构函数
(一)析构函数的特点
正文
一、 This指针
在c语言中我们调用函数初始化或进行一系列函数操作都要进行传值或传址调用,传址每次要取地址很是麻烦,于是C++祖师爷就像在C++上做一个优化,让编译器自己传地址而使用者只需要传参就行。This指针由此诞生了。
(一)使用方法:
(1)Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了⼀个隐含的this指针解决这⾥的问题。
用类创建对象后要进行初始化要写成以下的形式,个数必须与类中函数形参个数一模一样,不能少也不能多
(2)编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。
⽐如Date类的Init的真实原型为, void Init ( Date* const this, int year, int month, int day)而编译器规定this指针只能在形参第一个位置且不能显示写出来,但可以显示调用。也就是当你调用类的成员变量时前面可写可不写this->,或是混着写都可以。有了this指针我们就不需要传d1、d2的地址,也就实现了谁调用就打印谁。
(3)类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;
(4)C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针。(如上图Init 在参数部分没写this指针却可以用this指针)
(5)有些人可能会问this指针为什么要加const修饰,在C语言部分有提过const要是加在*号右边表示该指针不能被改变,若是在*左边表示指针指向的内容不能改变。Date*表示该指针是Date这个类的指针。
两道习题讲解:
(1)
p是空指针,空指针编译器不一定会报编译错误它有可能检查不到错误,A错。
空指针访问类中的函数不会报错,因为没有解引用只是访问一下没有关系
(2)
此题和上题类似,唯一的区别在于Print函数内部,类函数存在this指针所以它通过空指针访问了_a这是不允许的操作。
二、类的默认成员函数
默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。
第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?
三、构造函数
构造函数是特殊的成员函数,它不是开空间创建对象,而是对象实例化时自动初始化对象。
(一)构造函数的特点
(1)函数名与类名相同。
(2)⽆返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
(3)对象实例化时系统会⾃动调⽤对应的构造函数。
(4)构造函数可以重载。
以上四点是教我们如何构造构造函数,下图为构造函数重载:
重载我在前一篇博客有讲,同名类函数只要参数类型和参数个数不同就构成重载。
(5)那么我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错。(在没有构造函数情况下,要初始化这个成员变量,需要⽤初始化列表才能解决,初始化列表,我们下个章节再细细讲解)
内置类型:int 、float、 double 、指针.....
自定义类型:class、 struct、 union.....
比如下面这个日期类就得我们自己实现构造函数,因为日期类的成员变量全是内置类型,系统自动生成构造函数是随机值不符合预期。
那什么时候会自动生成有用的构造函数呢?如下图用两个栈实现一个队列,而两个栈已经开辟出来,它里面也有构造函数,那么我们自己要创建的队列MyQueue类就不用自己写构造,系统会自动调用自定义类型栈的构造函数。
什么叫默认构造函数?编译器自己生成的才是吗?
(6)如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数(其中一种),⼀旦⽤户显式定义编译器将不再⽣成。(默认构造函数后面第七点讲)
所以上面调的栈默认构造函数是不是编译器自己生成的呢?不是!栈里面的也是我们自己显示写的,如果把栈的默认构造屏蔽掉编译器调不了会报错。
只有一种情况自动生成的默认构造够我们用:这个类里面成员全是自定义类型,并且自定义类型已经有自己的默认构造函数
(7)⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认⽣成那个叫默认构造,实际上⽆参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调⽤的构造就叫默认构造。
所以默认构造函数并不是只指编译器自己生成的,编译器自己生成就是上图那两个栈的例子,前提是你栈里面本身就有构造函数,而我们创建的队列就不用写构造编译器直接去调栈的称为编译器默认⽣成的构造函数,换句话说如果栈里面没有构造函数,编译器又自己生不成就会报错。
下图自定义类型栈的构造函数是个带参的,不满足默认构造函数语法,那MyQueue要求调用自定义类型的构造就不能调了,因为不存在会报错。
回到我们不写时,编译器默认生成的函数行为是什么?
行为就是:编译器对内置类型没有要求,自定义类型调用它自己的构造函数。
四、析构函数
析构函数不是完成对对象本身的销毁,是完成对象中资源的清理释放工作。C++规定对象在销毁时会自动调用析构函数,完成资源释放这样就不怕忘记清理了。
(一)析构函数的特点
(1)析构函数名是在类名前加上字符 ~。
(2)⽆参数⽆返回值。 (这⾥跟构造类似,也不需要加void)
(3)⼀个类只能有⼀个析构函数。若未显式定义(自己定义),系统会⾃动⽣成默认的析构函数(系统生成的大部分不满足需求,和自动生成构造函数定义类似)
(4)对象⽣命周期结束时,系统会⾃动调⽤析构函数。可理解为要释放一块空间先把空间内数据清理干净再释放。
(5)跟构造函数类似,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,自定类型成员一定会调用他自己的析构函数。
还是以上面两个栈实现一个队列的例子举例:自定义类型栈的内部自己实现了构造函数和析构函数,所以我们自己创建的队列MyQueue就可以编译器自动生成调用栈的析构,简直是人生赢家。
同样要是栈里面没有写析构函数那么编译器对自定义类型就无法自动生成析构,编译器报错!
所以所谓的自动生成是在自定义类型有的情况下,本质还是要自己写,大家理解一下。
(6)还需要注意的是我们显示写析构函数,对于⾃定义类型成员也会调⽤他的析构。也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数。
(7)如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date;如果默认⽣成的析构就可以⽤,也就不需要显示写析构,如MyQueue;但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏,如Stack栈。
对日期类型不用写析构是因为它里面全是内置类型也没指向什么资源根本不需要释放,年月日属于对象的成员,函数结束栈帧销毁他就销毁了。
回到我们不写时,编译器默认生成的函数行为是什么?
行为就是:编译器对内置类型不做处理,自定义类型调用它自己的析构函数。
易忽略点:
(1)造顺序是按照语句的顺序进行构造,析构是按照构造的相反顺序进行析构
(2)构造函数可以是私有的,只是这样之后就不能直接实例化对象
(3)类的析构函数调用一般按照构造函数调用的相反顺序进行调用,但是要注意static对象的存在, 因为static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象
本节完