析构函数.ppt

上传人:文库蛋蛋多 文档编号:2205948 上传时间:2023-01-30 格式:PPT 页数:48 大小:209.50KB
返回 下载 相关 举报
析构函数.ppt_第1页
第1页 / 共48页
析构函数.ppt_第2页
第2页 / 共48页
析构函数.ppt_第3页
第3页 / 共48页
析构函数.ppt_第4页
第4页 / 共48页
析构函数.ppt_第5页
第5页 / 共48页
点击查看更多>>
资源描述

《析构函数.ppt》由会员分享,可在线阅读,更多相关《析构函数.ppt(48页珍藏版)》请在三一办公上搜索。

1、析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“”符号。在C+中“”是位取反运算符,从这点也可以想到:析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。具体地说如果出现以下几种情况,程序就会执行析构函数:如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。,7.5 析构函数,static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。如果定义

2、了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数)时,调用该全局对象的析构函数。如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。,析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。程序设计者事先设计好析构函数,以完成所需的功能,只要对象的生命期结束,程序就自动执行析构函数来完成这些工作。析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。,例 包含构造函数和析构函数的C+程序。#

3、include#includeusing namespace std;class Student/声明Student类public:Student(int n,string nam,char s)/定义构造函数num=n;name=nam;sex=s;coutConstructor called.endl;/输出有关信息Student()/定义析构函数coutDestructor called.endl;/输出有关信息void display()/定义成员函数coutnum:numendl;coutname:nameendl;coutsex:sexendlendl;,private:int n

4、um;string name;char sex;int main()Student stud1(10010,Wang_li,f);/建立对象stud1stud1.display();/输出学生1的数据 Student stud2(10011,Zhang_fun,m);/定义对象stud2stud2.display();/输出学生2的数据return 0;,程序运行结果如下:Constructor called.(执行stud1的构造函数)num:10010(执行stud1的display函数)name:Wang_lisex:fConstructor called.(执行stud2的构造函数)n

5、um:10011(执行stud2的display函数)name:Zhang_funsex:mDestructor called.(执行stud2的析构函数)Destructor called.(执行stud1的析构函数),在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序。在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。如图所示。,调用构造函数和析构函数的顺序,数组不仅可以由简单变量组成(例如整型数组的每一个元素都是整型变量),也可以由对象组成(对

6、象数组的每一个元素都是同类的对象)。在日常生活中,有许多实体的属性是共同的,只是属性的具体内容不同。例如一个班有50个学生,每个学生的属性包括姓名、性别、年龄、成绩等。如果为每一个学生建立一个对象,需要分别取50个对象名。用程序处理很不方便。这时可以定义一个“学生类”对象数组,每一个数组元素是一个“学生类”对象。例如 Student stud50;/假设已声明了Student类,定义stud数组,有50个元素,对象数组,在建立数组时,同样要调用构造函数。如果有50个元素,需要调用50次构造函数。在需要时可以在定义数组时提供实参以实现初始化。如果构造函数只有一个参数,在定义数组时可以直接在等号后

7、面的花括号内提供实参。如Student stud3=60,70,78;/合法,3个实参分别传递给3个数组元素的构造函数如果构造函数有多个参数,则不能用在定义数组时直接提供所有实参的方法,因为一个数组有多个元素,对每个元素要提供多个实参,如果再考虑到构造函数有默认参数的情况,很容易造成实参与形参的对应关系不清晰,出现歧义性。例如,类Student的构造函数有多个参数,且为默认参数:Student Student(int=1001,int=18,int=60);/定义构造函数,有多个参数,且为默认参数,如果定义对象数组的语句为Student stud3=1005,60,70;在程序中最好不要采用这

8、种容易引起歧义性的方法。编译系统只为每个对象元素的构造函数传递一个实参,所以在定义数组时提供的实参个数不能超过数组元素个数,如 Student stud3=60,70,78,45;/不合法,实参个数超过对象数组元素个数那么,如果构造函数有多个参数,在定义对象数组时应当怎样实现初始化呢?如果构造函数有3个参数,分别代表学号、年龄、成绩。则可以这样定义对象数组:Student Stud3=/定义对象数组Student(1001,18,87),/调用第1个元素的构造函数,为它提供3个实参Student(1002,19,76),/调用第2个元素的构造函数,为它提供3个实参,Student(1003,1

9、8,72)/调用第3个元素的构造函数,为它提供3个实参;在建立对象数组时,分别调用构造函数,对每个元素初始化。每一个元素的实参分别用括号包起来,对应构造函数的一组形参,不会混淆。例 对象数组的使用方法。#include using namespace std;class Boxpublic:Box(int h=10,int w=12,int len=15):height(h),width(w),length(len)/声明有默认参数的构造函数,用参数初始化表对数据成员初始化int volume();private:int height;int width;,int length;int Box

10、volume()return(height*width*length);int main()Box a3=/定义对象数组Box(10,12,15),/调用构造函数Box,提供第1个元素的实参Box(15,18,20),/调用构造函数Box,提供第2个元素的实参Box(16,20,26)/调用构造函数Box,提供第3个元素的实参;coutvolume of a0 is a0.volume()endl;/调用a0的volume函数coutvolume of a1 is a1.volume()endl;/调用a1 的volume函数coutvolume of a2 is a2.volume()end

11、l;/调用a2 的volume函数,运行结果如下:volume of a0 is 1800volume of a1 is 5400volume of a2 is 8320,前面讲过:每个对象中的数据成员都分别占有存储空间,如果对同一个类定义了n个对象,则有n组同样大小的空间以存放n个对象中的数据成员。但是,不同对象都调用同一个函数代码段。那么,当不同对象的成员函数引用数据成员时,怎么能保证引用的是所指定的对象的数据成员呢?假如,对于上面程序中定义的Box类,定义了3个同类对象a,b,c。如果有a.volume(),应该是引用对象a中的height,width和length,计算出长方体a的体积

12、。如果有b.volume(),应该是引用对象b中的height,width和length,计算出长方体b的体积。而现在都用同一个函数段,系统怎样使它分别引用a或b中的数据成员呢?,this 指针,在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。例如,当调用成员函数a.volume时,编译系统就把对象a的起始地址赋给this指针,于是在成员函数引用数据成员时,就按照this的指向找到对象a的数据成员。例如volume函数要计算height*width*length的值,实际上是执行:(this

13、-height)*(this-width)*(this-length)由于当前this指向a,因此相当于执行:(a.height)*(a.width)*(a.length)这就计算出长方体a的体积。同样如果有b.volume(),编译系统就把对象b的起始地址赋给成员函数volume的this指针,显然计算出来的是长方体b的体积。,this指针是隐式使用的,它是作为参数被传递给成员函数的。本来,成员函数volume的定义如下:int Boxvolume()return(height*width*length);C+把它处理为int Boxvolume(Box*this)return(this-h

14、eight*this-width*this-length);即在成员函数的形参表列中增加一个this指针。在调用该成员函数时,实际上是用以下方式调用的:a.volume(将对象a的地址传给形参this指针。然后按this的指向去引用其他成员。,需要说明:这些都是编译系统自动实现的,编程序者不必人为地在形参中增加this指针,也不必将对象a的地址传给this指针。在需要时也可以显式地使用this指针。例如在Box类的volume函数中,下面两种表示方法都是合法的、相互等价的。return(height*width*length);/隐含使用this指针return(this-height*thi

15、s-width*this-length);/显式使用this指针 可以用*this表示被调用的成员函数所在的对象,*this就是this所指向的对象,即当前的对象。例如在成员函数a.volume()的函数体中,如果出现*this,它就是本对象a。上面的return语句也可写成 return(*this).height*(*this).width*(*this).length);,注意*this两侧的括号不能省略,不能写成*this.height。所谓“调用对象a的成员函数f”,实际上是在调用成员函数f时使this指针指向对象a,从而访问对象a的成员。在使用“调用对象a的成员函数f”时,应当对它

16、的含义有正确的理解。,如果有n个同类的对象,那么每一个对象都分别有自己的数据成员,不同对象的数据成员各自有值,互不相干。但是有时人们希望有某一个或几个数据成员为所有对象所共有。这样可以实现数据共享。全局变量,它能够实现数据共享。如果在一个程序文件中有多个函数,在每一个函数中都可以改变全局变量的值,全局变量的值为各函数共享。但是用全局变量的安全性得不到保证,由于在各处都可以自由地修改全局变量的值,很有可能偶一失误,全局变量的值就被修改,导致程序的失败。因此在实际工作中很少使用全局变量。如果想在同类的多个对象之间实现数据共享,也不要用全局对象,可以用静态的数据成员。,静态成员,静态数据成员是一种特

17、殊的数据成员。它以关键字static开头。例如class Boxpublic:int volume();private:static int height;/把height定义为静态的数据成员int width;int length;如果希望各对象中的height的值是一样的,就可以把它定义为静态数据成员,这样它就为各对象所共有,而不只属于某个对象的成员,,静态数据成员,所有对象都可以引用它。静态的数据成员在内存中只占一份空间。每个对象都可以引用这个静态数据成员。静态数据成员的值对所有对象都是一样的。如果改变它的值,则在各对象中这个数据成员的值都同时改变了。这样可以节约空间,提高效率。说明:(

18、1)如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有在定义对象时,才为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。静态数据成员是在所有对象之外单独开辟空间。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。,在一个类中可以有一个或多个静态数据成员,所有的对象共享这些静态数据成员,都可以引用它。(2)如果在一个函数中定义了静态变量,在函数结束时该静态变量并不释放,仍然存在并保留其值。现在讨论的静态数据成员也是类似的,它不随对象的建立而分配空间,也不随对象的撤销而释放(一般数据

19、成员是在对象建立时分配空间,在对象撤销时释放)。静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间。(3)静态数据成员可以初始化,但只能在类体外进行初始化。如int Boxheight=10;/表示对Box类中的数据成员初始化,其一般形式为数据类型 类名静态数据成员名=初值;不必在初始化语句中加static。注意:不能用参数初始化表对静态数据成员初始化。如在定义Box类中这样定义构造函数是错误的:Box(int h,int w,int len):height(h)/错误,height是静态数据成员如果未对静态数据成员赋初值,则编译系统会自动赋予初值0。(4)静态数据成员既可以通过对

20、象名引用,也可以通过类名来引用。请观察下面的程序:引用静态数据成员。,#include using namespace std;class Boxpublic:Box(int,int);int volume();static int height;/把height定义为公用的静态的数据成员int width;int length;BoxBox(int w,int len)/通过构造函数对width和length赋初值width=w;length=len;int Boxvolume()return(height*width*length);int Boxheight=10;/对静态数据成员hei

21、ght初始化,int main()Box a(15,20),b(20,30);couta.heightendl;/通过对象名a引用静态数据成员coutb.heightendl;/通过对象名b引用静态数据成员coutBoxheightendl;/通过类名引用静态数据成员couta.volume()endl;/调用volume函数,计算体积,输出结果上面3个输出语句的输出结果相同(都是10)。这就验证了所有对象的静态数据成员实际上是同一个数据成员。请注意:在上面的程序中将height定义为公用的静态数据成员,所以在类外可以直接引用。可以看到在类外可以通过对象名引用公用的静态数据成员,也可以通过类名

22、引用静态数据成员。即使没有定义类对象,也可以通过类名引用静态数据成员。,这说明静态数据成员并不是属于对象的,而是属于类的,但类的对象可以引用它。如果静态数据成员被定义为私有的,则不能在类外直接引用,而必须通过公用的成员函数引用。(5)有了静态数据成员,各对象之间的数据有了沟通的渠道,实现数据共享,因此可以不使用全局变量。全局变量破坏了封装的原则,不符合面向对象程序的要求。但是也要注意公用静态数据成员与全局变量的不同,静态数据成员的作用域只限于定义该类的作用域内(如果是在一个函数中定义类,那么其中静态数据成员的作用域就是此函数内)。在此作用域内,可以通过类名和域运算符“”引用静态数据成员,而不论

23、类对象是否存在。,成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数。如static int volume();和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分。如果要在类外调用公用的静态成员函数,要用类名和域运算符“”。如 Boxvolume();实际上也允许通过对象名调用静态成员函数,如a.volume();但这并不意味着此函数是属于对象a的,而只是用a的类型而已。,静态成员函数,与静态数据成员不同,静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员。前面曾指出:当调用一个对象的成员函数(非静态成员函数)时,系统会把该对象的起

24、始地址赋给成员函数的this指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针。既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默认访问(即在引用数据成员时不指定对象名)。可以说,静态成员函数与非静态成员函数的根本区别是:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。,静态成员函数可以直接引用本类中的静态数据成员,因为静态成员同样是属于类的,可以直接引用。在C+程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。假如在一个静态成员函数中有以下语句:coutheigh

25、tendl;/若height已声明为static,则引用本类中的静态成员,合法coutwidthendl;/若width是非静态数据成员,不合法但是,并不是绝对不能引用本类中的非静态成员,只是不能进行默认访问,因为无法知道应该去找哪个对象。如果一定要引用本类的非静态成员,应该加对象名和成员运算符“.”。如couta.widthendl;/引用本类对象a中的非静态成员假设a已定义为Box类对象,且在当前作用域内有效,则此语句合法。,有关引用非静态成员的具体方法。静态成员函数的应用。#include using namespace std;class Student/定义Student类publi

26、c:Student(int n,int a,float s):num(n),age(a),score(s)/定义构造函数void total();static float average();/声明静态成员函数private:int num;int age;float score;static float sum;/静态数据成员static int count;/静态数据成员;void Studenttotal()/定义非静态成员函数sum+=score;/累加总分 count+;/累计已统计的人数,float Studentaverage()/定义静态成员函数return(sum/count

27、);float Studentsum=0;/对静态数据成员初始化int Studentcount=0;/对静态数据成员初始化int main()Student stud3=/定义对象数组并初始化Student(1001,18,70),Student(1002,19,78),Student(1005,20,98);int n;coutn;/输入需要求前面多少名学生的平均成绩 for(int i=0;in;i+)/调用3次total函数studi.total();coutthe average score of n students is Studentaverage()endl;/调用静态成员函

28、数return 0;,运行结果为please input the number of students:3the average score of 3 students is 82.3333说明:(1)在主函数中定义了stud对象数组,为了使程序简练,只定义它含3个元素,分别存放3个学生的数据。程序的作用是先求用户指定的n名学生的总分,然后求平均成绩(n由用户输入)。(2)在Student类中定义了两个静态数据成员sum(总分)和count(累计需要统计的学生人数),这是由于这两个数据成员的值是需要进行累加的,它们并不是只属于某一个对象元素,而是由各对象元素共享的,可以看出:它们的值是在不断变

29、化的,而且无论对哪个对象元素而言,都是相同的,而且始终不释放内存空间。,(3)total是公有的成员函数,其作用是将一个学生的成绩累加到sum中。公有的成员函数可以引用本对象中的一般数据成员(非静态数据成员),也可以引用类中的静态数据成员。score是非静态数据成员,sum和count是静态数据成员。(4)average是静态成员函数,它可以直接引用私有的静态数据成员(不必加类名或对象名),函数返回成绩的平均值。,在一个类中可以有公用的(public)成员和私有的(private)成员。在类外可以访问公用成员,只有本类中的函数可以访问本类的私有成员。现在,我们来补充介绍一个例外友元(frien

30、d)。友元可以访问与其有好友关系的类中的私有成员。友元包括友元函数和友元类。,友元,如果在本类以外的其他地方定义了一个函数(这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数),在类体中用friend对其进行声明,此函数就称为本类的友元函数。友元函数可以访问这个类中的私有成员。,友元函数,1.将普通函数声明为友元函数通过下面的例子可以了解友元函数的性质和作用。例 友元函数的简单例子。#include using namespace std;class Timepublic:Time(int,int,int);friend void display(Time,minute=m;se

31、c=s;void display(Time/调用display函数,实参t1是Time类对象程序输出结果如下:10:13:56,由于声明了display是Time类的friend函数,所以display函数可以引用Time中的私有成员hour,minute,sec。但注意在引用这些私有数据成员时,必须加上对象名,不能写成 couthour:minute:secendl;因为display函数不是Time类的成员函数,不能默认引用Time类的数据成员,必须指定要访问的对象。,2.友元成员函数friend函数不仅可以是一般函数(非成员函数),而且可以是另一个类中的成员函数。例 友元成员函数的简单应

32、用。在本例中除了介绍有关友元成员函数的简单应用外,还将用到类的提前引用声明。#include using namespace std;class Date;/对Date类的提前引用声明class Time/定义Time类public:Time(int,int,int);void display(Date,int minute;int sec;class Date/声明Date类public:Date(int,int,int);friend void Timedisplay(Date,void Timedisplay(Date,运行时输出:12/25/2004(输出Date类对象d1中的私有数据

33、)10:13:56(输出Time类对象t1中的私有数据)在本例中定义了两个类Time和Date。程序第3行是对Date类的声明,因为在第7行和第16行中对display函数的声明和定义中要用到类名Date,而对Date类的定义却在其后面。能否将Date类的声明提到前面来呢?也不行,因为在Date类中的第4行又用到了Time类,也要求先声明Time类才能使用它。为了解决这个问题,C+允许对类作“提前引用”的声明,即在正式声明一个类之前,先声明一个类名,表示此类将在稍后声明。程序第3行就是提前引用声明,它只包含类名,不包括类体。如果没有第3行,程序编译就会出错。,在一般情况下,对象必须先声明,然后

34、才能使用它。但是在特殊情况下(如上面例子所示的那样),在正式声明类之前,需要使用该类名。但是应当注意:类的提前声明的使用范围是有限的。只有在正式声明一个类以后才能用它去定义类对象。如果在上面程序第3行后面增加一行:Date d1;/企图定义一个对象会在编译时出错。因为在定义对象时是要为这些对象分配存储空间的,在正式声明类之前,编译系统无法确定应为对象分配多大的空间。编译系统只有在“见到”类体后,才能确定应该为对象预留多大的空间。,在一般情况下,两个不同的类是互不相干的。在本例中,由于在Date类中声明了Time类中的display成员函数是Date类的“朋友”,因此该函数可以引用Date类中所

35、有的数据。请注意在本程序中调用友元函数访问有关类的私有数据方法:(1)在函数名display的前面要加display所在的对象名(t1);(2)display成员函数的实参是Date类对象d1,否则就不能访问对象d1中的私有数据;(3)在Timedisplay函数中引用Date类私有数据时必须加上对象名,如d.month。,3.一个函数(包括普通函数和成员函数)可以被多个类声明为“朋友”,这样就可以引用多个类中的私有数据例如,可以将上例程序中的display函数不放在Time类中,而作为类外的普通函数,然后分别在Time和Date类中将display声明为朋友。在主函数中调用display函数

36、,display函数分别引用Time和Date两个类的对象的私有数据,输出年、月、日和时、分、秒。,不仅可以将一个函数声明为一个类的“朋友”,而且可以将一个类(例如B类)声明为另一个类(例如A类)的“朋友”。这时B类就是A类的友元类。友元类B中的所有函数都是A类的友元函数,可以访问A类中的所有成员。在A类的定义体中用以下语句声明B类为其友元类:friend B;声明友元类的一般形式为friend 类名;关于友元,有两点需要说明:,友元类,(1)友元的关系是单向的而不是双向的。(2)友元的关系不能传递。在实际工作中,除非确有必要,一般并不把整个类声明为友元类,而只将确实有需要的成员函数声明为友元函数,这样更安全一些。关于友元利弊的分析:面向对象程序设计的一个基本原则是封装性和信息隐蔽,而友元却可以访问其他类中的私有成员,不能不说这是对封装原则的一个小的破坏。但是它能有助于数据共享,能提高程序的效率,在使用友元时,要注意到它的副作用,不要过多地使用友元,只有在使用它能使程序精炼,并能大大提高程序的效率时才用友元。,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 建筑/施工/环境 > 项目建议


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号