讲授第9章中国高等院校计算机基础教育课程体系规划教材

上传人:仙*** 文档编号:47009778 上传时间:2021-12-16 格式:PPT 页数:107 大小:337KB
收藏 版权申诉 举报 下载
讲授第9章中国高等院校计算机基础教育课程体系规划教材_第1页
第1页 / 共107页
讲授第9章中国高等院校计算机基础教育课程体系规划教材_第2页
第2页 / 共107页
讲授第9章中国高等院校计算机基础教育课程体系规划教材_第3页
第3页 / 共107页
资源描述:

《讲授第9章中国高等院校计算机基础教育课程体系规划教材》由会员分享,可在线阅读,更多相关《讲授第9章中国高等院校计算机基础教育课程体系规划教材(107页珍藏版)》请在装配图网上搜索。

1、第第9章章 关于类和对象的进一步讨论关于类和对象的进一步讨论9.1 构造函数构造函数9.2 析构函数析构函数9.3 调用构造函数和析构函数的顺序调用构造函数和析构函数的顺序9.4 对象数组对象数组9.5 对象指针对象指针9.6 共用数据的保护共用数据的保护9.7 对象的动态建立和释放对象的动态建立和释放9.8 对象的赋值和复制对象的赋值和复制9.9 静态成员静态成员9.10 友元友元9.11 类模板类模板在建立一个对象时,常常需要作某些初始化的工作,在建立一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。例如对数据成员赋初值。如果一个数据成员未被赋如果一个数据成员未被赋值,则它的值

2、是不可预知的,因为在系统为它分配值,则它的值是不可预知的,因为在系统为它分配内存时,保留了这些存储单元的原状,这就成为了内存时,保留了这些存储单元的原状,这就成为了这些数据成员的初始值。这种状况显然是与人们的这些数据成员的初始值。这种状况显然是与人们的要求不相符的,对象是一个实体,它反映了客观事要求不相符的,对象是一个实体,它反映了客观事物的属性物的属性(例如时钟的时、分、秒的值例如时钟的时、分、秒的值),是应该有,是应该有确定的值的。确定的值的。注意:注意: 类的数据成员是不能在声明类时初始化的类的数据成员是不能在声明类时初始化的。9.1 构造函数构造函数 9.1.1 对象的初始化对象的初始

3、化如果一个类中所有的成员都是公用的,则可以在定如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化。义对象时对数据成员进行初始化。如如class Timepublic: /声明为公用成员声明为公用成员 hour; minute; sec;Time t1=14,56,30; /将将t1初始化为初始化为14:56:30这种情况和结构体变量的初始化是差不多的,在一这种情况和结构体变量的初始化是差不多的,在一个花括号内顺序列出各公用数据成员的值,两个值个花括号内顺序列出各公用数据成员的值,两个值之间用逗号分隔。之间用逗号分隔。但是,如果数据成员是私有的,但是,如果数据成员是私有的,

4、或者类中有或者类中有private或或protected的成员,就不能用这的成员,就不能用这种方法初始化。种方法初始化。在第在第8章的几个例子中,是用成员函数来对对象中章的几个例子中,是用成员函数来对对象中的数据成员赋初值的的数据成员赋初值的(例如例例如例8.3中的中的set_time函数函数)。从例从例8.3中可以看到,用户在主函数中调用中可以看到,用户在主函数中调用set_time函数来为数据成员赋值。函数来为数据成员赋值。如果对一个类定义了多个如果对一个类定义了多个对象,而且类中的数据成员比较多,那么,程序就对象,而且类中的数据成员比较多,那么,程序就显得非常臃肿烦琐。显得非常臃肿烦琐。

5、为了解决这个问题,为了解决这个问题,C+提供了提供了构造函数构造函数(constructor)来来处理对象的初始化处理对象的初始化。构造函数是一。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在用户来调用它,而是在建立对象时自动执行建立对象时自动执行。构造构造函数的名字必须与类名同名函数的名字必须与类名同名,而不能由用户任意命,而不能由用户任意命名,以便编译系统能识别它并把它作为构造函数处名,以便编译系统能识别它并把它作为构造函数处理。理。它不具有任何类型,不返回任何值。它不具有任何类型,不返回任何值。构造函数构造函数的功

6、能是由用户定义的,用户根据初始化的要求设的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数。计函数体和函数参数。9.1.2 构造函数的作用构造函数的作用例例9.1 在例在例8.3基础上定义构造成员函数。基础上定义构造成员函数。#include using namespace std;class Timepublic:Time( ) /定义构造成员函数,函数名与类名相同定义构造成员函数,函数名与类名相同hour=0; /利用构造函数对对象中的数据成员赋初值利用构造函数对对象中的数据成员赋初值minute=0;sec=0;void set_time( ); /函数声明函数声明void

7、show_time( ); /函数声明函数声明private:int hour; /私有数据成员私有数据成员int minute;int sec;void Time set_time( ) /定义成员函数,向数据成员赋值定义成员函数,向数据成员赋值cinhour;cinminute;cinsec;void Time show_time( ) /定义成员函数,输出数据成员的值定义成员函数,输出数据成员的值 couthour:minute:secendl;int main( )Time t1; /建立对象建立对象t1,同时调用构造函数,同时调用构造函数t1.Time( )t1.set_time(

8、); /对对t1的数据成员赋值的数据成员赋值t1.show_time( ); /显示显示t1的数据成员的值的数据成员的值 Time t2; /建立对象建立对象t2,同时调用构造函数,同时调用构造函数t2.Time( )t2.show_time( ); /显示显示t2的数据成员的值的数据成员的值return 0;程序运行的情况为:程序运行的情况为: 10 25 54 (从键盘输入新值赋给从键盘输入新值赋给t1的数据成员的数据成员)10:25:54 (输出输出t1的时、分、秒值的时、分、秒值)0:0:0 (输出输出t2的时、分、秒值的时、分、秒值)上面是在类内定义构造函数的,也可以只在类内对上面是

9、在类内定义构造函数的,也可以只在类内对构造函数进行声明而在类外定义构造函数构造函数进行声明而在类外定义构造函数。将程序。将程序中的第中的第47行改为下面一行:行改为下面一行: Time( ); /对构造函数进行声明对构造函数进行声明在类外定义构造函数:在类外定义构造函数:Time Time( ) /在类外定义构造成员函数,要加上类名在类外定义构造成员函数,要加上类名Time和域限定和域限定符符“ ”hour=0; minute=0;sec=0;有关构造函数的使用,有以下说明:有关构造函数的使用,有以下说明: (1) 在类对象进入其作用域时调用构造函数在类对象进入其作用域时调用构造函数。(2)

10、构造函数没有返回值构造函数没有返回值,因此也不需要在定义构,因此也不需要在定义构造函数时声明类型,这是它和一般函数的一个重要造函数时声明类型,这是它和一般函数的一个重要的不同之点。的不同之点。(3) 构造函数不需用户调用,也不能被用户调用构造函数不需用户调用,也不能被用户调用。(4) 在构造函数的函数体中不仅可以对数据成员赋在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句。初值,而且可以包含其他语句。但是一般不提倡在但是一般不提倡在构造函数中加入与初始化无关的内容,以保持程序构造函数中加入与初始化无关的内容,以保持程序的清晰。的清晰。(5) 如果用户自己没有定义构造函数,则如

11、果用户自己没有定义构造函数,则C+系统系统会自动生成一个构造函数,只是这个构造函数的函会自动生成一个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行初始化操作数体是空的,也没有参数,不执行初始化操作。在例在例9.1中构造函数不带参数,在函数体中对数据中构造函数不带参数,在函数体中对数据成员赋初值。这种方式使该类的每一个对象都得到成员赋初值。这种方式使该类的每一个对象都得到同一组初值同一组初值(例如例例如例9.1中各数据成员的初值均为中各数据成员的初值均为0)。但是有时用户希望对但是有时用户希望对不同的对象赋予不同的初值不同的对象赋予不同的初值。可以可以采用带参数的构造函数采用带参数

12、的构造函数,在调用不同对象的构在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化。以实现不同的初始化。构造函数首部的一般格式为构造函数首部的一般格式为构造函数名构造函数名(类型类型 1 形参形参1,类型,类型2 形参形参2,)前面已说明:前面已说明: 用户是不能调用构造函数的,因此用户是不能调用构造函数的,因此无法采用常规的调用函数的方法给出实参。无法采用常规的调用函数的方法给出实参。实参是实参是在定义对象时给出的。在定义对象时给出的。定义对象的一般格式为定义对象的一般格式为 类名类名 对象名对象名(实参实参1,实参,实

13、参2,);9.1.3 带参数的构造函数带参数的构造函数例例9.2 有两个长方柱,其长、宽、高分别为:有两个长方柱,其长、宽、高分别为: (1)12,20,25;(2)10,14,20。求它们的体积。编一个。求它们的体积。编一个基于对象的程序,在类中用带参数的构造函数。基于对象的程序,在类中用带参数的构造函数。#include using namespace std;class Boxpublic:Box(int,int,int); /声明带参数的构造函数声明带参数的构造函数int volume( ); /声明计算体积的函数声明计算体积的函数 private:int height;int wid

14、th;int length;Box Box(int h,int w,int len) /在类外定义带参数的构造函数在类外定义带参数的构造函数height=h;width=w;length=len;int Box volume( ) /定义计算体积的函数定义计算体积的函数return(height*width*length);int main( )Box box1(12,25,30); /建立对象建立对象box1,并指定,并指定box1长、宽、高的值长、宽、高的值coutThe volume of box1 is box1.volume( )endl;Box box2(15,30,21); /建

15、立对象建立对象box2,并指定,并指定box2长、宽、高的值长、宽、高的值coutThe volume of box2 is box2.volume( )endl;return 0;程序运行结果如下:程序运行结果如下: The volume of box1 is 9000The volume of box2 is 9450可以知道:可以知道: (1) 带参数的构造函数中的形参,其对应的实参带参数的构造函数中的形参,其对应的实参在定义对象时给定在定义对象时给定。(2) 用这种方法可以方便地实现对不同的对象进用这种方法可以方便地实现对不同的对象进行不同的初始化行不同的初始化。在在9.1.3节中介绍

16、的是在构造函数的函数体内通过赋节中介绍的是在构造函数的函数体内通过赋值语句对数据成员实现初始化。值语句对数据成员实现初始化。C+还提供另一种还提供另一种初始化数据成员的方法初始化数据成员的方法参数初始化表参数初始化表来实现对来实现对数据成员的初始化。这种方法不在函数体内对数据数据成员的初始化。这种方法不在函数体内对数据成员初始化,而是在函数首部实现。例如例成员初始化,而是在函数首部实现。例如例9.2中中定义构造函数可以改用以下形式:定义构造函数可以改用以下形式: Box Box(int h,int w,int len):height(h),width(w),length(len) 这种写法方便

17、、简练,尤其当需要初始化的数据成这种写法方便、简练,尤其当需要初始化的数据成员较多时更显其优越性。甚至可以直接在类体中员较多时更显其优越性。甚至可以直接在类体中(而不是在类外而不是在类外)定义构造函数。定义构造函数。9.1.4 用参数初始化表对数据成员初始化用参数初始化表对数据成员初始化在一个类中可以在一个类中可以定义多个构造函数定义多个构造函数,以便对类对象,以便对类对象提供不同的初始化的方法提供不同的初始化的方法,供用户选用。这些构造,供用户选用。这些构造函数具有函数具有相同的名字相同的名字,而,而参数的个数或参数的类型参数的个数或参数的类型不相同不相同。这称为构造函数的重载。这称为构造函

18、数的重载。在第在第4章第章第4.6节节中所介绍的函数重载的知识也适用于构造函数。中所介绍的函数重载的知识也适用于构造函数。通过下面的例子可以了解怎样应用构造函数的重载。通过下面的例子可以了解怎样应用构造函数的重载。9.1.5 构造函数的重载构造函数的重载例例9.3 在例在例9.2的基础上,定义两个构造函数,其中的基础上,定义两个构造函数,其中一个无参数,一个有参数。一个无参数,一个有参数。#include using namespace std;class Boxpublic:Box( ); /声明一个无参的构造函数声明一个无参的构造函数Box(int h,int w,int len):hei

19、ght(h),width(w),length(len) /声明一个有参的构造函数,用参数的初始化表对数据成员初始化声明一个有参的构造函数,用参数的初始化表对数据成员初始化int volume( );private:int height;int width;int length;Box Box( ) /定义一个无参的构造函数定义一个无参的构造函数height=10;width=10;length=10;int Box volume( )return(height*width*length);int main( )Box box1; /建立对象建立对象box1,不指定实参不指定实参coutThe

20、volume of box1 is box1.volume( )endl;Box box2(15,30,25); /建立对象建立对象box2,指定指定3个实参个实参coutThe volume of box2 is box2.volume( )endl;return 0; 在本程序中定义了两个重载的构造函数,其实还可在本程序中定义了两个重载的构造函数,其实还可以定义其他重载构造函数,其原型声明可以为以定义其他重载构造函数,其原型声明可以为Box Box(int h); /有有1个参数的构造函数个参数的构造函数Box Box(int h,int w); /有两个参数的构造函数有两个参数的构造函数

21、在建立对象时分别给定在建立对象时分别给定1个参数和个参数和2个参数。个参数。说明:说明: (1) 调用构造函数时不必给出实参的构造函数,称调用构造函数时不必给出实参的构造函数,称为为默认构造函数默认构造函数(default constructor)。显然,无参。显然,无参的构造函数属于默认构造函数。一个类只能有一个的构造函数属于默认构造函数。一个类只能有一个默认构造函数。默认构造函数。(2) 如果在建立对象时选用的是无参构造函数,应如果在建立对象时选用的是无参构造函数,应注意正确书写定义对象的语句。注意正确书写定义对象的语句。(3) 尽管在一个类中可以包含多个构造函数,但是尽管在一个类中可以包

22、含多个构造函数,但是对于每一个对象来说,建立对象时对于每一个对象来说,建立对象时只执行其中一个只执行其中一个构造函数构造函数,并非每个构造函数都被执行。,并非每个构造函数都被执行。构造函数中参数的值既可以通过实参传递,也可以构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户指定为某些默认值,即如果用户不指定实参值不指定实参值,编,编译系统就译系统就使形参取默认值使形参取默认值。前面介绍过在函数中可以使用有默认值的参数。在前面介绍过在函数中可以使用有默认值的参数。在构造函数中也可以采用这样的方法来实现初始化。构造函数中也可以采用这样的方法来实现初始化。9.1.6 使用默认参

23、数的构造函数使用默认参数的构造函数在例在例9.3程序的基础上改写如下:程序的基础上改写如下:#include using namespace std;class Boxpublic:Box(int h=10,int w=10,int len=10); /在声明构造函数时指定默认参数在声明构造函数时指定默认参数int volume( ); private:int height;int width;int length;Box Box(int h,int w,int len) /在定义函数时可以不指定默认参数在定义函数时可以不指定默认参数height=h;width=w;length=len;in

24、t Box volume( )return(height*width*length);int main( )Box box1; /没有给实参没有给实参 coutThe volume of box1 is box1.volume( )endl;Box box2(15); /只给定一个实参只给定一个实参coutThe volume of box2 is box2.volume( )endl;Box box3(15,30); /只给定只给定2个实参个实参coutThe volume of box3 is box3.volume( )endl;Box box4(15,30,20); /给定给定3个实参

25、个实参coutThe volume of box4 is box4.volume( )endl;return 0;程序运行结果为程序运行结果为The volume of box1 is 1000The volume of box2 is 1500The volume of box3 is 4500The volume of box4 is 9000程序中对构造函数的定义程序中对构造函数的定义(第第1216行行)也可以改写也可以改写成参数初始化表的形式:成参数初始化表的形式: Box Box(int h,int w,int len):height(h),width(w),length(len)

26、可以看到:可以看到: 在构造函数中使用默认参数是方便而在构造函数中使用默认参数是方便而有效的,它提供了建立对象时的多种选择,它的有效的,它提供了建立对象时的多种选择,它的作作用相当于好几个重载的构造函数。用相当于好几个重载的构造函数。它的好处是:它的好处是: 即使在调用构造函数时没有提供实参值,不仅不会即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初出错,而且还确保按照默认的参数值对对象进行初始化。尤其在希望对每一个对象都有同样的初始化始化。尤其在希望对每一个对象都有同样的初始化状况时用这种方法更为方便。状况时用这种方法更为方便。说明:说明: (1) 应

27、该在应该在声明构造函数时指定默认值声明构造函数时指定默认值,而不能只,而不能只在定义构造函数时指定默认值。在定义构造函数时指定默认值。(2) 程序第程序第5行在声明构造函数时,形参名可以省略。行在声明构造函数时,形参名可以省略。(3) 如果构造函数的全部参数都指定了默认值,则如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给在定义对象时可以给一个或几个实参,也可以不给出实参。出实参。(4) 在一个类中定义了全部是默认参数的构造函数在一个类中定义了全部是默认参数的构造函数后,后,不能再定义重载构造函数不能再定义重载构造函数。析构函数析构函数(destructor

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

29、象),当这个,当这个函数被调用结束时函数被调用结束时,对象,对象应该释放,在对象释放前自动执行析构函数。应该释放,在对象释放前自动执行析构函数。9.2 析构函数析构函数static局部对象局部对象在函数调用结束时对象并不释放,在函数调用结束时对象并不释放,因此也不调用析构函数,只在因此也不调用析构函数,只在main函数结束函数结束或或调调用用exit函数结束程序时函数结束程序时,才调用,才调用static局部对象的析局部对象的析构函数。构函数。如果定义了一个如果定义了一个全局对象全局对象,则在,则在程序的程序的流程离开其作用域时流程离开其作用域时(如如main函数结束或调用函数结束或调用exi

30、t函函数数) 时,调用该全局对象的析构函数。时,调用该全局对象的析构函数。如果用如果用new运算符运算符动态地建立了一个对象,当用动态地建立了一个对象,当用delete运运算符释放该对象算符释放该对象时,先调用该对象的析构函数时,先调用该对象的析构函数。析构函数的作用并不是删除对象,而是在撤销对象析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。程序设计者事先设可以被程序分配给新对象使用。程序设计者事先设计好析构函数,以完成所需的功能,只要对象的生计好析构函数,以完成所需的功能,只要对

31、象的生命期结束,程序就自动执行析构函数来完成这些工命期结束,程序就自动执行析构函数来完成这些工作。作。析构函数不返回任何值,没有函数类型,也没有函析构函数不返回任何值,没有函数类型,也没有函数参数。它不能被重载。一个类可以有多个构造函数参数。它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。数,但只能有一个析构函数。实际上,析构函数的作用并不仅限于释放资源方面,实际上,析构函数的作用并不仅限于释放资源方面,它还可以被用来执行它还可以被用来执行“用户希望在最后一次使用对用户希望在最后一次使用对象之后所执行的任何操作象之后所执行的任何操作”,例如输出有关的信息。例如输出有关的信息。这里

32、说的用户是指类的设计者,因为,析构函数是这里说的用户是指类的设计者,因为,析构函数是在声明类的时候定义的。也就是说,析构函数可以在声明类的时候定义的。也就是说,析构函数可以完成类的设计者所指定的任何操作。完成类的设计者所指定的任何操作。一般情况下,一般情况下,类的设计者应当在声明类的同时定义类的设计者应当在声明类的同时定义析构函数,以指定如何完成析构函数,以指定如何完成“清理清理”的工作。的工作。如果如果用户没有定义析构函数,用户没有定义析构函数,C+编译系统会自动生成编译系统会自动生成一个析构函数,但它只是徒有析构函数的名称和形一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都

33、不进行。想让析构函数完成式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。任何工作,都必须在定义的析构函数中指定。例例9.5 包含构造函数和析构函数的包含构造函数和析构函数的C+程序。程序。#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

34、( ) /定义析构函数定义析构函数coutDestructor called.endl; /输出有关信息输出有关信息void display( ) /定义成员函数定义成员函数coutnum: numendl;coutname: nameendl;coutsex: sexendlendl; private:int num;char name10;char sex;int main( )Student stud1(10010,Wang_li,f); /建立对象建立对象stud1stud1.display( ); /输出学生输出学生1的数据的数据 Student stud2(10011,Zhang_

35、fun,m); /定义对象定义对象stud2stud2.display( ); /输出学生输出学生2的数据的数据return 0;程序运行结果如下:程序运行结果如下: Constructor called. (执行执行stud1的构造函数的构造函数)num: 10010 (执行执行stud1的的display函数函数)name:Wang_lisex: fConstructor called. (执行执行stud2的构造函数的构造函数)num: 10011 (执行执行stud2的的display函数函数)name:Zhang_funsex:mDestructor called. (执行执行stu

36、d2的析构函数的析构函数)Destructor called. (执行执行stud1的析构函数的析构函数)在使用构造函数和析构函数时,需要在使用构造函数和析构函数时,需要特别注意对它特别注意对它们的调用时间和调用顺序们的调用时间和调用顺序。在一般情况下,在一般情况下,调用析构函数的次序正好与调用构调用析构函数的次序正好与调用构造函数的次序相反:造函数的次序相反: 最先被调用的构造函数,其最先被调用的构造函数,其对应的对应的(同一对象中的同一对象中的)析构函数最后被调用,而最析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调后被调用的构造函数,其对应的析构函数最先被调用。用。如

37、图如图9.1示意。示意。9.3 调用构造函数和析构函数的顺序调用构造函数和析构函数的顺序图图9.1但是,并不是在任何情况下都是按这一原则处理的。但是,并不是在任何情况下都是按这一原则处理的。在第在第4章第章第4.11和和4.12节中曾介绍过作用域和存储类节中曾介绍过作用域和存储类别的概念,这些概念对于对象也是适用的。对象可别的概念,这些概念对于对象也是适用的。对象可以在不同的作用域中定义,可以有不同的存储类别。以在不同的作用域中定义,可以有不同的存储类别。这些会影响调用构造函数和析构函数的时机。这些会影响调用构造函数和析构函数的时机。下面归纳一下什么时候调用构造函数和析构函数:下面归纳一下什么

38、时候调用构造函数和析构函数: (1) 在在全局范围中定义的对象全局范围中定义的对象(即在所有函数之外定即在所有函数之外定义的对象义的对象),它的构造函数在文件中的所有函数,它的构造函数在文件中的所有函数(包包括括main函数函数)执行之前调用。但如果一个程序中有执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。这些对象的构造函数的执行顺序是不确定的。当当main函数执行完毕或调用函数执行完毕或调用exit函数时函数时(此时程序终此时程序终止止),调用析构函数。,调用析构函数。(2) 如

39、果定义的是如果定义的是局部自动对象局部自动对象(例如在函数中定义例如在函数中定义对象对象),则在建立对象时调用其构造函数。如果函,则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函函数。在函数调用结束、对象释放时先调用析构函数。数。(3) 如果在函数中定义如果在函数中定义静态静态(static)局部对象局部对象,则只,则只在程序第一次调用此函数建立对象时调用构造函数在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用一次,在调用结束时对象并不

40、释放,因此也不调用析构函数,只析构函数,只在在main函数结束或调用函数结束或调用exit函数结束函数结束程序时,才调用析构函数程序时,才调用析构函数。构造函数和析构函数在面向对象的程序设计中是相构造函数和析构函数在面向对象的程序设计中是相当重要的。以上介绍了最基本的、使用最多的普通当重要的。以上介绍了最基本的、使用最多的普通构造函数,在本章第构造函数,在本章第9.8节中将会介绍节中将会介绍复制构造函复制构造函数数,在第,在第10章第章第10.7节中还要介绍节中还要介绍转换构造函数转换构造函数。数组不仅可以由简单变量组成数组不仅可以由简单变量组成(例如整型数组的每例如整型数组的每一个元素都是整

41、型变量一个元素都是整型变量),也可以由对象组成也可以由对象组成(对象对象数组的每一个元素都是同类的对象数组的每一个元素都是同类的对象)。在日常生活中,有许多实体的属性是共同的,只是在日常生活中,有许多实体的属性是共同的,只是属性的具体内容不同。例如一个班有属性的具体内容不同。例如一个班有50个学生,每个学生,每个学生的属性包括姓名、性别、年龄、成绩等。如个学生的属性包括姓名、性别、年龄、成绩等。如果为每一个学生建立一个对象,需要分别取果为每一个学生建立一个对象,需要分别取50个对个对象名。用程序处理很不方便。这时可以定义一个象名。用程序处理很不方便。这时可以定义一个“学生类学生类”对象数组,每

42、一个数组元素是一个对象数组,每一个数组元素是一个“学学生类生类”对象。例如对象。例如 Student stud50; /假设已声明了假设已声明了Student类,定义类,定义stud数组,有数组,有50个元素个元素9.4 对象数组对象数组在建立数组时,同样要调用构造函数。如果有在建立数组时,同样要调用构造函数。如果有50个个元素,需要调用元素,需要调用50次构造函数。次构造函数。在需要时可以在定在需要时可以在定义数组时提供实参以实现初始化。义数组时提供实参以实现初始化。如果构造函数如果构造函数只只有一个参数有一个参数,在定义数组时可以直接在等号后面的,在定义数组时可以直接在等号后面的花括号内提

43、供实参。花括号内提供实参。如如Student stud3=60,70,78; /合法,合法,3个实参分别传递给个实参分别传递给3个数组元素的构造个数组元素的构造函数函数如果构造函数如果构造函数有多个参数有多个参数,则不能用在定义数组时,则不能用在定义数组时直接提供所有实参的方法,直接提供所有实参的方法,因为一个数组有多个元因为一个数组有多个元素,对每个元素要提供多个实参,如果再考虑到构素,对每个元素要提供多个实参,如果再考虑到构造函数有造函数有默认参数默认参数的情况,很容易造成实参与形参的情况,很容易造成实参与形参的对应关系不清晰,出现歧义性。例如,类的对应关系不清晰,出现歧义性。例如,类St

44、udent的构造函数有多个参数,且为的构造函数有多个参数,且为默认参数默认参数: Student Student(int=1001,int=18,int=60); /定义构造函数,有多个参数,且定义构造函数,有多个参数,且为默认参数为默认参数如果定义对象数组的语句为如果定义对象数组的语句为Student stud3=1005,60,70;编译系统编译系统只为每个对象元素的构造函数传递一个实只为每个对象元素的构造函数传递一个实参,参,所以在定义数组时提供的所以在定义数组时提供的实参个数不能超过数实参个数不能超过数组元素个数组元素个数,如如 Student stud3=60,70,78,45; /

45、不合法,实参个数超过对象数组元素个数不合法,实参个数超过对象数组元素个数那么,那么,如果构造函数有多个参数,在定义对象数组如果构造函数有多个参数,在定义对象数组时应当怎样实现初始化呢?时应当怎样实现初始化呢?回答是:回答是: 在花括号中在花括号中分别写出构造函数并指定实参。分别写出构造函数并指定实参。如果构造函数有如果构造函数有3个参数,分别代表学号、年龄、成绩。则可以这样个参数,分别代表学号、年龄、成绩。则可以这样定义对象数组:定义对象数组: Student Stud3= /定义对象数组定义对象数组Student(1001,18,87), /调用第调用第1个元素的构造函数,为它提供个元素的构

46、造函数,为它提供3个实参个实参Student(1002,19,76), /调用第调用第2个元素的构造函数,为它提供个元素的构造函数,为它提供3个实参个实参Student(1003,18,72) /调用第调用第3个元素的构造函数,为它提供个元素的构造函数,为它提供3个实参个实参;在建立对象数组时,分别调用构造函数,对每个元在建立对象数组时,分别调用构造函数,对每个元素初始化。每一个元素的实参分别用括号包起来,素初始化。每一个元素的实参分别用括号包起来,对应构造函数的一组形参,不会混淆对应构造函数的一组形参,不会混淆。例例9.6 对象数组的使用方法。对象数组的使用方法。#include using

47、 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 volume( )return(height*width*length);int main( ) Box a3= /定义对象数组定义对象数组Box(10,12,15),

48、 /调用构造函数调用构造函数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( )endl; /调用调用

49、a2 的的volume函数函数运行结果如下:运行结果如下: volume of a0 is 1800volume of a1 is 5400volume of a2 is 8320在建立对象时,编译系统会为每一个对象分配一定在建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放其成员。的存储空间,以存放其成员。对象空间的起始地址对象空间的起始地址就是对象的指针。就是对象的指针。可以定义一个指针变量,用来存可以定义一个指针变量,用来存放对象的指针。如果有一个类:放对象的指针。如果有一个类:class Timepublic:int hour;int minute;int sec;void

50、get_time( );9.5 对象指针对象指针 9.5.1 指向对象的指针指向对象的指针void Time get_time( )couthour:minute:sechour pt所指向的对象中的所指向的对象中的hour成员,即成员,即t1.hour(*pt).get_time ( ) 调用调用pt所指向的对象中的所指向的对象中的get_time函数,即函数,即t1.get_timept-get_time ( ) 调用调用pt所指向的对象中的所指向的对象中的get_time函数,即函数,即t1.get_time对象有地址,对象有地址,存放对象初始地址的指针变量存放对象初始地址的指针变量就是

51、就是指指向对象的指针变量向对象的指针变量。对象中的成员也有地址,。对象中的成员也有地址,存放存放对象成员地址的指针变量对象成员地址的指针变量就是就是指向对象成员的指针指向对象成员的指针变量。变量。9.5.2 指向对象成员的指针指向对象成员的指针1. 指向对象数据成员的指针指向对象数据成员的指针定义指向对象数据成员的指针变量的方法和定义指定义指向对象数据成员的指针变量的方法和定义指向普通变量的指针变量方法相同。例如向普通变量的指针变量方法相同。例如int *p1; /定义指向整型数据的指针变量定义指向整型数据的指针变量定义指向对象数据成员的指针变量的一般形式为定义指向对象数据成员的指针变量的一般

52、形式为数据类型名数据类型名 *指针变量名;指针变量名;如果如果Time类的数据成员类的数据成员hour为公用的整型数据,为公用的整型数据,则可以在类外通过指向对象数据成员的指针变量访则可以在类外通过指向对象数据成员的指针变量访问对象数据成员问对象数据成员hour。p1=&t1.hour; /将对象将对象t1的数据成员的数据成员hour的地址赋给的地址赋给p1,p1指向指向t1.hourcout*p1endl; /输出输出t1.hour的值的值2. 指向对象成员函数的指针指向对象成员函数的指针定义指向对象成员函数的指针变量的方法和定义指定义指向对象成员函数的指针变量的方法和定义指向普通函

53、数的指针变量方法有所不同。向普通函数的指针变量方法有所不同。成员函数与普通函数有一个最根本的区别:成员函数与普通函数有一个最根本的区别: 它是它是类中的一个成员。类中的一个成员。编译系统要求在上面的赋值语句编译系统要求在上面的赋值语句中,指针变量的类型必须与赋值号右侧函数的类型中,指针变量的类型必须与赋值号右侧函数的类型相匹配,要求在以下相匹配,要求在以下3方面都要匹配:方面都要匹配: 函数参数函数参数的类型和参数个数;的类型和参数个数;函数返回值的类型;函数返回值的类型;所属所属的类。的类。定义指向成员函数的指针变量采用下面的形式:定义指向成员函数的指针变量采用下面的形式: void (Ti

54、me *p2)( ); /定义定义p2为指向为指向Time类中公用成员函数的指针变量类中公用成员函数的指针变量定义指向公用成员函数的指针变量的一般形式为定义指向公用成员函数的指针变量的一般形式为 数据类型名数据类型名 (类名类名 *指针变量名指针变量名)(参数表列参数表列);可以让它指向一个公用成员函数,只需把公用成员可以让它指向一个公用成员函数,只需把公用成员函数的入口地址赋给一个指向公用成员函数的指针函数的入口地址赋给一个指向公用成员函数的指针变量即可。如变量即可。如 p2=&Time get_time;使指针变量指向一个公用成员函数的一般形式为使指针变量指向一个公用成员函数的一般

55、形式为 指针变量名指针变量名=&类名类名 成员函数名成员函数名;例例9.7 有关对象指针的使用方法。有关对象指针的使用方法。#include using namespace std;class Timepublic:Time(int,int,int);int hour;int minute;int sec;void get_time( ); /声明公有成员函数声明公有成员函数;Time Time(int h,int m,int s)hour=h;minute=m;sec=s;void Time get_time( ) /定义公有成员函数定义公有成员函数couthour:minute:

56、secendl;int main( )Time t1(10,13,56); /定义定义Time类对象类对象t1int *p1=&t1.hour; /定义指向整型数据的指针变量定义指向整型数据的指针变量p1,并使,并使p1指向指向t1.hourcout*p1get_time( ); /调用调用p2所指向对象所指向对象(即即t1)的的get_time函数函数void (Time *p3)( ); /定义指向定义指向Time类公用成员函数的指针变量类公用成员函数的指针变量p3p3=&Time get_time; /使使p3指向指向Time类公用成员函数类公用成员函数get_time(

57、t1.*p3)( ); /调用对象调用对象t1中中p3所指的成员函数所指的成员函数(即即t1.get_time( )程序运行结果为程序运行结果为10 (main函数第函数第4行的输出行的输出)10:13:56 (main函数第函数第5行的输出行的输出)10:13:56 (main函数第函数第7行的输出行的输出)10:13:56 (main函数第函数第10行的输出行的输出)可以看到为了输出可以看到为了输出t1中中hour,minute和和sec的值,可的值,可以采用以采用3种不同的方法。种不同的方法。说明:说明: (1) 从从main函数第函数第9行可以看出:行可以看出: 成员函数的入口成员函数

58、的入口地址的正确写法是:地址的正确写法是: &类名类名 成员函数名成员函数名。(2) main函数第函数第8、9两行可以合写为一行:两行可以合写为一行: void (Time *p3)( )=&Time get_time; /定义指针变量定义指针变量时指定其指向时指定其指向前面曾经提到过:前面曾经提到过: 每个对象中的数据成员都分别每个对象中的数据成员都分别占有存储空间,如果对同一个类定义了占有存储空间,如果对同一个类定义了n个对象,个对象,则有则有n组同样大小的空间以存放组同样大小的空间以存放n个对象中的数据成个对象中的数据成员。但是,不同对象都调用同一个函数代码段。员。但是

59、,不同对象都调用同一个函数代码段。那么,当不同对象的成员函数引用数据成员时,怎那么,当不同对象的成员函数引用数据成员时,怎么能保证引用的是所指定的对象的数据成员呢?假么能保证引用的是所指定的对象的数据成员呢?假如,对于例如,对于例9.6程序中定义的程序中定义的Box类,定义了类,定义了3个同个同类对象类对象a,b,c。如果有。如果有a.volume( ),应该是引用对象,应该是引用对象a中的中的height,width和和length,计算出长方体,计算出长方体a的体的体积。如果有积。如果有b.volume( ),应该是引用对象,应该是引用对象b中的中的height,width和和length

60、,计算出长方体,计算出长方体b的体积。的体积。而现在都用同一个函数段,系统怎样使它分别引用而现在都用同一个函数段,系统怎样使它分别引用a或或b中的数据成员呢?中的数据成员呢?9.5.3 this 指针指针在每一个成员函数中都包含一个特殊的指针,这个在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定指针的名字是固定的,的,称为称为this。它是指向本类对。它是指向本类对象的指针,它的值是象的指针,它的值是当前被调用的成员函数所在的当前被调用的成员函数所在的对象的起始地址对象的起始地址。例如,当调用成员函数例如,当调用成员函数a.volume时,编译系统就把对象时,编译系统就把对象a的起

61、始地址赋给的起始地址赋给this指针,指针,于是在成员函数引用数据成员时,就按照于是在成员函数引用数据成员时,就按照this的指的指向找到对象向找到对象a的数据成员。的数据成员。例如例如volume函数要计算函数要计算height*width*length的值,实际上是执行:的值,实际上是执行: (this-height)*(this-width)*(this-length)由于当前由于当前this指向指向a,因此相当于执行:,因此相当于执行: (a.height)*(a.width)*(a.length)这就计算出长方体这就计算出长方体a的体积。同样如果有的体积。同样如果有b.volume(

62、 ),编译系统就把对象,编译系统就把对象b的起始地址赋给的起始地址赋给成员函数成员函数volume的的this指针,显然计算出来的是长指针,显然计算出来的是长方体方体b的体积。的体积。this指针是隐式使用的,它是作为参数被传递给成员函数的。指针是隐式使用的,它是作为参数被传递给成员函数的。本来,成员函数本来,成员函数volume的定义如下:的定义如下: int Box volume( )return (height*width*length); C+把它处理为把它处理为int Box volume(Box *this)return(this-height * this-width * thi

63、s-length); 需要说明:需要说明: 这些都是编译系统自动实现的,编程序者不必这些都是编译系统自动实现的,编程序者不必人为地在形参中增加人为地在形参中增加this指针,也不必将对象指针,也不必将对象a的地址传给的地址传给this指针指针。用前面介绍的方法定义的对象是静态的,在程序运用前面介绍的方法定义的对象是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。但行过程中,对象所占的空间是不能随时释放的。但有时人们希望在需要用到对象时才建立对象,在不有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间需要用该对象时就撤销它,释放它所占的内存空间以供

64、别的数据使用。这样可以供别的数据使用。这样可提高内存空间的利用率提高内存空间的利用率。前面介绍了用前面介绍了用new运算符动态分配内存,用运算符动态分配内存,用delete运算符释放内存空间。运算符释放内存空间。这也适用于对象,可以用这也适用于对象,可以用new运算符动态建立对象,用运算符动态建立对象,用delete运算符撤销对运算符撤销对象。象。如果已经定义了一个如果已经定义了一个Box类,可以用下面的方法动类,可以用下面的方法动态地建立一个对象:态地建立一个对象: 9.7 对象的动态建立和释放对象的动态建立和释放new Box;编译系统开辟了一段内存空间,并在此内存空间中编译系统开辟了一段

65、内存空间,并在此内存空间中存放一个存放一个Box类对象,同时调用该类的构造函数,类对象,同时调用该类的构造函数,以使该对象初始化以使该对象初始化(如果已对构造函数赋予此功能如果已对构造函数赋予此功能的话的话)。但是此时用户还无法访问这个对象,因为但是此时用户还无法访问这个对象,因为这个对象既没有对象名,用户也不知道它的地址。这个对象既没有对象名,用户也不知道它的地址。这种对象称为无名对象,它确实是存在的,但它没这种对象称为无名对象,它确实是存在的,但它没有名字。有名字。用用new运算符动态地分配内存后,将运算符动态地分配内存后,将返回一个指向返回一个指向新对象的指针的值新对象的指针的值,即所分

66、配的内存空间的起始地,即所分配的内存空间的起始地址。址。用户可以获得这个地址,并通过这个地址来访用户可以获得这个地址,并通过这个地址来访问这个对象。问这个对象。需要定义一个指向本类的对象的指针需要定义一个指向本类的对象的指针变量来存放该地址。如变量来存放该地址。如Box *pt; /定义一个指向定义一个指向Box类对象的指针变量类对象的指针变量ptpt=new Box; /在在pt中存放了新建对象的起始地址中存放了新建对象的起始地址在程序中就可以通过在程序中就可以通过pt访问这个新建的对象。如访问这个新建的对象。如 coutheight; /输出该对象的输出该对象的height成员成员coutvolume( ); /调用该对象的调用该对象的volume函数,计算并输出体积函数,计算并输出体积C+还允许在执行还允许在执行new时,对新建立的对象进行初时,对新建立的对象进行初始化。如始化。如 Box *pt=new Box(12,15,18);这种写法是把上面两个语句这种写法是把上面两个语句(定义指针变量和用定义指针变量和用new建立新对象建立新对象)合并为一个语句,并指定初值。这样合并为一个语句,并指定初值。这样更精炼。新对象中的更精炼。新对

展开阅读全文
温馨提示:
1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
2: 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
3.本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!