第三章面向对象的程序设计精选文档.ppt
第三章面向对象的程序设计第三章面向对象的程序设计1本讲稿第一页,共一百七十一页本章导读 理解继承的概念和意义,理解单一继承、多重继承。理解并掌握派生类构造函数的编写要求,以及派生类对象的构造过程和机理。掌握虚函数和多态性的概念,掌握虚函数的定义方法、调用方法及其在实现多态性方面所起到的作用。了解纯虚函数与抽象基类的概念。了解类的静态成员(静态数据成员和静态成员函数)的概念、定义方法及其作用。了解友元函数与友元类的概念、定义方法及其作用。了解运算符重载及在程序中实现运算符重载的方法。了解模板的概念,在程序中如何定义类模板和函数模板。本讲稿第二页,共一百七十一页3.1 类与对象的定义类和对象是面向对象程序设计(OOP)的两个最基本概念。所谓对象就是客观事物在计算机中的抽象描述;类是对具有相似属性和行为的一组对象的统一描述。3.1.1 类的定义类的定义C+的类是在结构体的基础上扩充而来的。类是把各种不同类型的数据(称为数据成员)和对数据的操作(成员函数)组织在一起而形成的用户自定义的数据类型。C+中,类定义包括类说明和类实现两大部分。说明部分提供了对该类所有数据成员和成员函数的描述,而实现部分提供了所有成员函数的实现代码。本讲稿第三页,共一百七十一页3.1 类与对象的定义类定义的一般形式为:class类名private:数据成员或成员函数protected:数据成员或成员函数public:数据成员或成员函数;本讲稿第四页,共一百七十一页3.1 类与对象的定义说明:说明:1.class是定义类的关键字,类名由用户自己定名,必须是C+的有效标识符,但一般首字母大写。2.大括号的部分是类的成员(数据成员和函数成员),它们分成三部分,分别由private、public、proctected三个关键字后跟冒号来指定。这三部分可以任何顺序出现,且在一个类的定义中,这三部分并非必须同时出现。(1)如果数据成员或成员函数在类的private部分,那么在类之外是不能存取的,只有类中的成员函数才能存取private的数据成员和成员函数。(2)在一个类的public部分说明的数据成员或成员函数可被程序中的任何函数或语句存取,public成员多为成员函数,用来提供一个与外界的接口,外界只有通过这个接口才可以实现对private成员的存取。本讲稿第五页,共一百七十一页3.1 类与对象的定义(3)在类的protected部分说明的数据成员和成员函数是不能在类之外存取的,只有类的成员函数及其子类(派生类)可以存取protected的成员。(4)当定义类时,当未指明成员是哪部分时,默认是属于private成员,但一般不要采用默认形式。如:下例中定义描述图书的类定义classRecordprivate:/private成员charbookname20;/数据成员bookname,/用于表示图书的名称intnumber;/数据成员number,表示图书编号本讲稿第六页,共一百七十一页3.1 类与对象的定义public:/public成员voidregist(char*a,intb);/成员函数regist,用于给/各数据成员赋值voidshow();/成员函数show,显示各数据成员的值;3.要特别注意,在类的定义中,类的说明部分的右边大括号后面必须有一“;”.4.根据类的定义,可看出:类是实现封装的工具,所谓封装就是将类的成员按使用或存取的方式分类,有条件地限制对类成员的使用,而封装是通过public和private与成员函数实现的。private的成员构成类的内部状态,public的成员则构成与外界通信的接口,通过public的成员函数来使用private的数据成员,从而在C+中实现了封装。本讲稿第七页,共一百七十一页3.1 类与对象的定义3.1.2 成员函数的定义成员函数的定义类中的成员函数可以在以下两处定义:(1)将成员函数的定义直接写在类中)将成员函数的定义直接写在类中:如:对于前面定义的图书类Record来说,其成员函数regist和show的定义可直接写在类的定义体中。classRecordprivate:charbookname20;intnumber;本讲稿第八页,共一百七十一页3.1 类与对象的定义public:voidregist(char*a,intb)/成员函数regist()的定义strcpy(bookname,a);/给数据成员bookname赋值number=b;/给数据成员number赋值voidshow()/成员函数show()的定义cout”名称:”booknameendl;cout”号码:”numberendl;本讲稿第九页,共一百七十一页3.1 类与对象的定义在类中直接定义成员函数的情况一般适合于成员函数规模较小的情况,也就是说它们一般为内联函数,即使没有明确用inline关键字。(2)在在类类的的定定义义体体中中只只写写出出成成员员函函数数的的原原型型说说明明,而成员函数的定义写在类的定义之后,这种情况比较适合于成员函数体较大的情况,但这时要求在定义成员函数时,在函数的名称之前加上其所属性类名及作用域运算符“:”。定义成员函数的一般类型为:返回值类型类名:成员函数名(参数说明)类体本讲稿第十页,共一百七十一页3.1 类与对象的定义此处的:符号叫作用域运算符,用它来指明哪个函数属于哪个类或哪个数据属于哪个类,所以使用类中成员的全名是:类名:成员名。而如果没有类名,则为全局数据或全局函数(非成员函数),也就是说类名是其成员名的一部分。如classRecordprivate:charbookname20;intnumber;public:voidregist(char*a,intb);/成员函数regist的原型voidshow();/成员函数show的原型;/定义图书类Record本讲稿第十一页,共一百七十一页3.1 类与对象的定义voidRecord:regist(char*a,intb)/regist()是类Record的/成员函数strcpy(bookname,a);number=b;voidRecord:show()/show()是类Record的成员函数cout”名称:”booknameendl;cout”号码:”numberendl;此外,目前开发程序的通常将类的定义写在一个头文件(.h文件)中,成员函数的定义写在一个程序文件(.cpp文件)中,这样,就相当于把类的定义(头文件)看成是类的外部接口,类的成员函数的定义看成类的内本讲稿第十二页,共一百七十一页3.1 类与对象的定义部实现。如:对上例可改成将类的定义体写在myapp.h文件中,而成员函数的定义体写在另外一个文件myapp.cpp中:/myapp.h文件classRecordprivate:charbookname20;intnumber;public:voidregist(char*a,intb);voidshow();;本讲稿第十三页,共一百七十一页3.1 类与对象的定义/myapp.cpp文件#include“iostream.h”#include“myapp.h”/一定不要忘记嵌入该头文件voidrecord:regist(char*a,intb)strcpy(bookname,a);number=b;voidrecord:show()cout”名称:”booknameendl;cout”号码:”number”。3.任何对对象私有数据的访问都必须通过向对象发送消息来实现,而且所发送的消息还必须是该对象能够识别和接受的。在C+中,消息发送正是通过公有成员函数的调用来实现的。由于类接口隐藏了对象的内部细节,用户只能通过类接口访问对象,因此,在类设计中必须提供足够的公有接口以捕获对象的全部行为,这正是类设计中的一个最基本的要求。4.上例中,在对象调用book1.regist(“C+编程教程”,1001);时,成员函数regist除了接受两个实参外,还接本讲稿第十九页,共一百七十一页3.1 类与对象的定义受了一个对象book1的地址,这个地址被一个隐含的形参this指针所获取,它等同于执行this=&book1,所以所有对数据成员的访问都隐含地被加上前缀:this-,因此,在成员函数体regist中,执行 strcpy(bookname,a);number=b;就等价于strcpy(this-bookname,a);this-number=b;这样,上例中的成员函数regist也可这样定义:void record:regist(char*a,int b)strcpy(this-bookname,a);this-number=b;通过以上手段就确保了不同对象调用成员函数时访问的是不同对象的数据,而它们之间没有干扰。本讲稿第二十页,共一百七十一页3.1 类与对象的定义3.1.5 对象赋值语句对象赋值语句 对于同一个类生成的两个对象,可以进行赋值,其功能是将一个对象的数据成员赋值到另一个对象中去,赋值语句的左右两边各是一个对象名:【例例3-13-1】对于类example的两个对象obj1和obj2,让obj2的成员数据的值等于obj1的成员数据的值(假定obj1的成员数据num已经存有数据215)。本讲稿第二十一页,共一百七十一页3.1 类与对象的定义#include#include/定义类classexampleprivate:/数据成员intnum;public:/函数成员说明voidset(inti)num=i;voiddisp()coutnnum=num;本讲稿第二十二页,共一百七十一页3.1 类与对象的定义/主程序voidmain()exampleobj1,obj2;obj1.set(215);obj1.disp();obj2=obj1;/对象赋值语句obj2.disp();coutendlendl;3.1.6 对象的作用域与生存期对象的作用域与生存期对象是类的实例,它实质就是某种数据类型的变量,在不同的位置以不同的方式定义对象时,其作用域和生存期是不同的。本讲稿第二十三页,共一百七十一页3.1 类与对象的定义如:classDesk/定义Desk类public:intweight;inthigh;intwidth;intlength;classStool/定义Stool类public:intweight;inthigh;intwidth;intlength;本讲稿第二十四页,共一百七十一页3.1 类与对象的定义deskda;/定义全局对象Stoolsa;voidfn()staticStoolss;/静态局部对象deskda;/定义局部对象/1局部对象(不包括局部静态对象)局部对象(不包括局部静态对象)其作用域是定义它的函数体,生存期从函数调用开始到函数调用结束,下一次再重新调用函数时,再重新构造对象。构造局部对象的次序(即分配存储单元)是按它们在函数体中声明的顺序。本讲稿第二十五页,共一百七十一页3.1 类与对象的定义2静态对象(局部静态和全局静态)静态对象(局部静态和全局静态)其作用域是定义它的函数体或程序文件,其生存期是整个程序。构造静态对象的次序是按它们在程序中出现的次序先后,并在整个程序运行开始时(即在主函数运行前)只构造一次。3全局对象全局对象全局对象的作用域是整个程序,生存期是整个程序的运行时间。它也是在程序运行前(即在主函数运行前)只构造一次。4类中成员的构造次序是以类中声明成员的次序进行。类中成员的构造次序是以类中声明成员的次序进行。构造函数和析构函数是类的两种特殊的成员函数。本讲稿第二十六页,共一百七十一页3.2 构造函数与析构函数3.2.1 构造函数构造函数构造函数(constructor)是与类名同名的特殊的成员函数,当定义该类的对象时,构造函数将被自动调用以实现对该对象的初始化。构造函数不能有返回值,因而不能指定包括void在内的任何返回值类型。构造函数的定义体可与其它成员函数成员一样,放在类内或类外都可。构造函数的定义格式为:类名(形参说明)类名(形参说明)函数体函数体 构造函数既可定义成有参函数,也可义成无参函数,要根据问题的需要来定。全局变量和静态变量在定义时,将自动赋初值为0;局部变量在定义时,其初始值不固定的。而当对象被定义时,由于对象的意义表达了现实世界的实本讲稿第二十七页,共一百七十一页3.2 构造函数与析构函数体,所以一旦定义对象,就必须有一个有意义的初始值,在C+中,在定义对象的同时,给该对象初始化的方法就是利用构造函数。如:【例【例3-2】类person包括4个数据成员,用来记录人员信息。生成对象obj,并使用构造函数为obj赋予初始值。#include#includeclassPerson/定义类private:/类Person的数据成员charname10;/姓名intage;/年龄intsalary;/薪金chartel8;/电话本讲稿第二十八页,共一百七十一页3.2 构造函数与析构函数public:/构造函数PersonPerson(char*xname,intxage,intxsalary,char*xtel);voiddisp();/函数Person的定义Person:Person(char*xname,intxage,intxsalary,char*xtel)strcpy(name,xname);/给各数据成员提供初值age=xage;salary=xsalary;strcpy(tel,xtel);本讲稿第二十九页,共一百七十一页3.2 构造函数与析构函数/函数disp的定义voidPerson:disp()coutendl;cout姓名:nameendl;cout年龄:ageendl;cout工资:salaryendl;cout电话:telendlendl;/主函数voidmain()/生成对象obj并初始化Personobj(张立三,25,850,45672314);/显示objobj.disp();本讲稿第三十页,共一百七十一页3.2 构造函数与析构函数程序的执行结果是:姓名:张立三 年龄:25 工资:850 电话:45672314在主函数中的Personobj(张立三,25,850,45672314);中完成了以下几个功能:1.定义并生成了对象obj。2.在生成对象obj的同时,自动调用相应类的构造函数Person3.将初始值张立三,25,850,45672314传递给构造函数Person相应的形参xname,xage,xsalary,xtel。4.执行构造函数体,将相应的值赋给相应的数据成员。本讲稿第三十一页,共一百七十一页3.2 构造函数与析构函数3.2.2 构造函数的重载构造函数的重载如果一个类中出现了两个以上的同名的成员函数时,称为类的成员函数的重载。【例例3-3】类rec定义两个重载函数,其中一个是无参函数,另一个是有参函数。它们都是构造函数。#include#include/定义类classRecprivate:charbookname30;intnumber;本讲稿第三十二页,共一百七十一页3.2 构造函数与析构函数public:Rec();/第1个构造函数说明Rec(char*a,intb);/第2个构造函数说明voidshow();Rec:Rec()/第1个构造函数定义strcpy(bookname,0);number=0;Rec:Rec(char*a,intb)/第2个构造函数定义strcpy(bookname,a);number=b;本讲稿第三十三页,共一百七十一页3.2 构造函数与析构函数voidRec:show()/show的函数定义coutbooknameis:booknameendl;coutbooknumberis:numberendl;voidmain()/主程序Recmybook(“VisualC+6.0”,10020);/自动调用构造/函数Rec(char*a,intb)mybook.show();Recyourbook;/自动调用构造函数Rec()yourbook.show();本讲稿第三十四页,共一百七十一页3.2 构造函数与析构函数程序的执行结果是:booknameis:VisualC+6.0booknumberis:10020booknameis:nonamebooknumberis:0可见,当出现构造函数重载时,其匹配方式同普通函数重载时的匹配方式。本讲稿第三十五页,共一百七十一页3.2 构造函数与析构函数3.2.3 默认构造函数与缺省构造函数默认构造函数与缺省构造函数C+规定,每个类必须有一个构造函数。如果在类中没有显式定义构造函数时,则C+编译系统在编译时为该类提供一个默认的构造函数,该默认构造函数是个无参函数,它仅负责创建对象,而不做任何初始化工作。只要一个类定义了一个构造函数(不一定是无参构造函数),C+编译系统就不再提供默认的构造函数。与变量定义相似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的默认值为0,否则对象的初始值是不定的。当构造函数有缺省参数时,称为具有缺省参数的构造函数,在使用时要防止二义性。本讲稿第三十六页,共一百七十一页3.2 构造函数与析构函数如:classMyclass/定义类Myclassprivate:intmember;public:Myclass();Myclass(inti);Myclass:Myclass()/构造函数Myclassmember=10;本讲稿第三十七页,共一百七十一页3.2 构造函数与析构函数Myclass:Myclass(inti=10)/构造函数Myclass(inti),该函数/的形参i为缺省参数member=i;voidmain()Myclassx(20);Myclassy;/产生二义性,无法确定自动调用哪个构造/函数完成对象的构造本讲稿第三十八页,共一百七十一页3.2 构造函数与析构函数3.2.4 析构函数析构函数当一个对象被定义时,系统自动调用构造函数为该对象分配相应的资源,当对象使用完毕后,这些系统资源需要在对象消失前被释放。析构函数是类的一个特殊成员函数,其函数名称是在类名的前面加上,它没有返回值,没有参数,不能随意调用,也没有重载,只是在类对象生命期结束时,系统自动调用。析构函数的定义方式为:类名()函数体注:(1)一个类中只能拥有一个析构函数。本讲稿第三十九页,共一百七十一页3.2 构造函数与析构函数(2)如果程序员在定义类时,没有为类提供析构函数,则系统会自动创建一个默认的析构函数,其形式为:类名()(3)对于一个简单的类来说,大多可以直接使用系统提供的默认析构函数。但是,如果在类的对象中分配有动态内存(如:用new申请分配的内容)时,就必须为该类提供适当的析构函数,完成清理工作。(4)对象被析构的顺序与对象建立时的顺序正好相反。即最后构造的对象先被析构。本讲稿第四十页,共一百七十一页3.2 构造函数与析构函数【例例3-4】类Teacher的构造函数为name申请存储空间,在析构函数中释放该空间。#include#include/定义类classTeacherprivate:char*name;intage;public:/说明构造函数Teacher本讲稿第四十一页,共一百七十一页3.2 构造函数与析构函数Teacher(char*i,inta)name=newcharstrlen(i)+1;/用new为name成员分配堆内存strcpy(name,i);age=a;coutn执行构造函数Teacherendl;/说明析构函数TeacherTeacher()deletename;cout执行析构函数Teacherendlendl;voidshow();本讲稿第四十二页,共一百七十一页3.2 构造函数与析构函数voidTeacher:show()cout姓名:name年龄:ageendl;voidmain()/主程序Teacherobj(张立三,25);obj.show();程序的执行结果是:执行构造函数Teacher姓名:张立三年龄:25执行析构函数Teacher本讲稿第四十三页,共一百七十一页3.2 构造函数与析构函数3.2.5 拷贝构造函数拷贝构造函数拷贝构造函数是C+中引入的一种新的构造函数。定义一个拷贝构造函数的方式是:类名(const类名&形式参数)函数体由此可看出:(1)拷贝构造函数的名称与类的名称相同,且它只有一个参数,该参数就是对该类对象的引用。(2)拷贝构造函数的功能是用于实现对象值的拷贝,通过将一个同类对象的值拷贝给一个新对象,来完成对新对象的初始化,即用一个对象去构造另外一个对象。本讲稿第四十四页,共一百七十一页3.2 构造函数与析构函数【例例3-5】Example是一个人员信息类。用普通构造函数生成obj1,用拷贝构造函数生成obj2。#include#includeclassExampleprivate:char*name;intnum;public:example(inti,char*str)/构造函数定义name=str;num=i;本讲稿第四十五页,共一百七十一页3.2 构造函数与析构函数example(constExample&x)/拷贝构造函数定义num=x.num;voidlist()/定义显示函数listcout数据成员num的值=numendlendl;voidmain()exampleobj1(215,“张立三”);/调用函数Example(inti,char*str)构造obj1exampleobj2(obj1);/使用拷贝构造函数构造obj2obj2.list();/显示obj2的值/其它程序部分本讲稿第四十六页,共一百七十一页3.2 构造函数与析构函数程序的执行结果是:数据成员num的值=215数据成员num的值=215说明:(1)上例中在main函数中的语句Example obj2(obj1);在执行时,系统会自动调用类Example的拷贝构造函数完成对obj2对象的构造。(2)如果程序员没有为所设计的类提供显式的拷贝构造函数,则系统会自动提供一个默认的拷贝构造函数,其功能是:把作为参数的对象的数据成员逐个拷贝到目标变量中,这称为成员级复制(或浅拷贝)。本讲稿第四十七页,共一百七十一页3.2 构造函数与析构函数3.2.6 一个类的对象作为另一个类的数据成员一个类的对象作为另一个类的数据成员一个类中的数据成员除了可以是int,char,float等这些基本的数据类型外,还可以是某一个类的一个对象。用子对象创建新类。在C+中,当把一个类的对象作为新类的数据员时,则新类的定义格式可表示为:classX类名1成员名1;类名2成员名2;类名n成员名n;/其它成员;本讲稿第四十八页,共一百七十一页3.2 构造函数与析构函数(3)如果一个类A的对象作为另一个类B的数据成员,则在类B的对象创建过程中,调用其构造函数的过程中,数据成员(类A的对象)会自动调用类A的构造函数。但应注意:如果类A的构造函数为有参函数时,则在程序中必须在类B的构造函数的括号后面加一“:”和被调用的类A的构造函数,且调用类A的构造函数时的实参值必须来自类B的形参表中的形参。这种方法称为初始化表的方式调用构造函数。如:以上面定义的类X为例,在对类X的对象进行初始化时,必须首先初始化其中的子对象,即必须首先调用这些子对象的构造函数。因此,类X的构造函数的定义格式应为:X:X(参数表0):成员1(参数表1),成员2(参数表2),成员n(参数表n)本讲稿第四十九页,共一百七十一页3.2 构造函数与析构函数其中,参数表1提供初始化成员1所需的参数,参数表2提供初始化成员2所需的参数,依此类推。并且这几个参数表的中的参数均来自参数表0,另外,初始化X的非对象成员所需的参数,也由参数表0提供。在构造新类的对象过程中,系统首先调用其子对象的构造函数,初始化子对象;然后才执行类X自己的构造函数,初始化类中的非对象成员。对于同一类中的不同子对象,系统按照它们在类中的说明顺序调用相应的构造函数进行初始化,而不是按照初始化表的顺序。本讲稿第五十页,共一百七十一页3.2 构造函数与析构函数【例例3-6】以下定义了三个Student、Teacher和Tourpair,其中Student类的对象和Teacher类的对象作为了Tourpair的数据成员,观察对象的构造过程和构造函数被执行的顺序。#includeclassStudentpublic:Student()cout”constructstudent.n”;semeshours=100;gpa=3.5;本讲稿第五十一页,共一百七十一页3.2 构造函数与析构函数protected:intsemeshours;floatgpa;classTeacherpublic:Teacher()cout”constructTeacher.n”;本讲稿第五十二页,共一百七十一页3.2 构造函数与析构函数classTourpairpublic:Tourpair()cout”constructtourpair.n”;nomeeting=0;protected:Studentstudent;Teacherteacher;intnomeeting;本讲稿第五十三页,共一百七十一页3.2 构造函数与析构函数voidmain()Tourpairtp;cout”backinmain.n”;其执行结果是:其执行结果是:constructstudent.constructteacher.constructtourpair.backinmain.由此可见:主函数main()运行开始时,遇到要创建Tourpair类的对象,于是调用其构造函数Tourpair(),该构造启动时,首先分配对象空间(包含一个Student对本讲稿第五十四页,共一百七十一页3.2 构造函数与析构函数象、一个Teacher对象和一个int型数据),然后根据其在类中声明的对象成员的次序依次调用其构造函数。即先调用Student()构造函数,后调用Teacher()构造函数,最后才执行它自己的构造函数的函数体。由于上例中Tourpair类的数据成员student和teacher的构造函数都是无参函数,所以系统在构造student和teacher对象时会自动调用各自的构造函数Student()和Teacher(),而不需要用初始化表的方式去调用。【例【例3-7】试分析以下程序的执行结果:#include#include本讲稿第五十五页,共一百七十一页3.2 构造函数与析构函数classStudentpublic:Student(char*pName=Noname)cout构造新同学:pNameendl;strncpy(name,pName,sizeof(name);namesizeof(name)-1=0;Student(Student&s)cout构造copyofs.nameendl;strcpy(name,copyof);strcat(name,s.name);本讲稿第五十六页,共一百七十一页3.2 构造函数与析构函数Student()cout析构nameendl;protected:charname40;classTutorpublic:Tutor(Student&s):student(s)/此为初始化表,调用/Student的拷贝构造函数cout构造指导教师n;protected:Studentstudent;本讲稿第五十七页,共一百七十一页3.2 构造函数与析构函数voidmain()Studentst1;/此处调用Student的构造函数Student(char*pName=Noname)Studentst2(zhang);/同上Tutortutor(st2);/此处调用Tutor的构造函数Tutor(Student&s)/在构造tutor对象的过程中,用初始化表调用/Student类的拷贝构造函数Student(Student&s)执行结果如下:构造新同学:Noname构造新同学:zhang构造copyofzhang本讲稿第五十八页,共一百七十一页3.2 构造函数与析构函数构造指导教师析构copyofzhang析构zhang析构Noname3.2.7 利用初始化表对常量数据成员或引用成员提供初值利用初始化表对常量数据成员或引用成员提供初值如前所述,构造函数可对对象的数据成员进行初始化,但若数据成员为常量成员或引用成员时,就有所不同,如:classSillyclasspublic:Sillyclass()/此构造函数对成员ten和refi的初始化错误。ten=10;refi=i;本讲稿第五十九页,共一百七十一页3.2 构造函数与析构函数protected:constintten;/常量数据成员tenint&refi;/引用refi;说明:说明:1.造成以上错误的原因是在Sillyclass类的构造函数进入之后(开始执行其函数体时),对象结构已经建立,数据成员ten和refi已存在,而其数据成员ten为const,而refi为引用,所以在构造函数体内不能再对其指派新的值。2.解决以上问题的方法是利用初始化表:在构造函数的括号后面加一“:”和初始化表,初始化表的格式是:数据成员名(值),如果有多个时,需要用逗号隔开。本讲稿第六十页,共一百七十一页3.2 构造函数与析构函数【例例3-8】类employee中包括私有数据成员x,和2个公有函数成员example、show。程序中使用初始化表是x(215)。#include#include/定义类employeeclassemployeeprivate:constintx;public:employee();voidshow();本讲稿第六十一页,共一百七十一页3.2 构造函数与析构函数/employee的类外定义employee:employee():x(215)/初始化表/show()的定义。voidemployee:show()coutnx的值是:xendl;/主函数voidmain()/生成对象并为x赋予初始值employeeobj;/调用show显示x的值obj.show();本讲稿第六十二页,共一百七十一页3.2 构造函数与析构函数3.2.8 3.2.8 类作用域类作用域 类作用域又可称为类域,它是指在类定义中用一对大括号所括起来的范围。由于在程序文件中可包含类,而类中又包含函数,因此,类域显然是一个小于文件域,而大于函数域的概念。由于在一个类中既可定义变量(数据成员),又可定义函数(成员函数),所以,类域在许多方面与文件域相似。但是,在类域中定义的变量不能使用auto、register和extern等修饰符,而且在类域中定义的函数也不能使用extern修饰符。同时,在类域中定义的静态成员和成员函数还具有外部的连接属性。本讲稿第六十三页,共一百七十一页3.2 构造函数与析构函数【例【例3-93-9】类域及其成员引用举例,设以下程序代码被存放到了一个程序文件中。#includeclassMyclassprivate:intx;inty;public:Myclass(inta,intb)x=a;y=b;voidprint();voidmyfunc();本讲稿第六十四页,共一百七十一页3.2 构造函数与析构函数voidMyclass:print()coutx=x,y=yendl;voidMyclass:myfunc()intx=9,y=10;coutInmyfunc:x=x,y=yendl;/输出局部变量/输出类的数据成员coutMyclass:x=Myclass:x,Myclass:y=Myclass:ymyfunc();程序的运行结果为:x=100,y=200Inmyfunc:x=9,y=10Myclass:x=100,Myclass:y=200说明:说明:(1)类成员函数的原型在类的定义体中声明,具有类作用域,但其实现部分在类的定义体外。由于不同类的成员函数可以具有相同的名字,因此,需要用作用域运算符“:”来指明该成员函数所属的类。本讲稿第六十六页,共一百七十一页3.2 构造函数与析构函数(2)类中的成员拥有类作用域,因此在成员函数中可以直接引用类的数据成员。但是,如果在成员函数中定义了同名的局部变量时,则必须用作用域运算符“:”来指定,以免混乱。如:上例中的myfunc()函数中定义了与类的数据成员同名的局部变量x、y,所以在myfunc()函数中要访问类中的数据成员x和y的值时,必须加上作用域运算符。(3)类中的成员拥有类的作用域,如果要从类外访问类的成员时,则必须通过对象名或指向对象的指针。当通过对象名时,应使用圆点成员选择符“.”;当通过指针时,应使用箭头成员选择符“-”。如上例中的test.print();与ptest-myfunc();本讲稿第六十七页,共一百七十一页3.3 继承和派生3.3.1 继承的概念继承的概念一个类的数据成员和成员函数,有些是类本身自己定义的,有一些是可继承的或通过模板生成的。所谓继承(inheritance)就是利用已有的数据类型定义出新的数据类型。利用类的“继承”,就可以将原来的程序代码重复使用,从而减少了程序代码的冗余度,符合软件重用的目标。所以说,继承是面向对象程序设计的一个重要机制。另外,在C+中扩充派生类成员的方法是非常灵活的。派生类不仅可以继承原来类的成员,还可以通过以下方式产生新的成员:本讲稿第六十八页,共一百七十一页3.3 继承和派生(1)增加新的数据成员;(2)增加新的成员函数;(3)重新定义已有成员函数;(4)改变现有成员的属性。在继承关系中,称被继承的类为基类(baseclass)(或父类),而把通过继承关系定义出来的新类称为派生类(derivedclass)(子类)。由此可见,派生类既可以对基类的性质进行扩展,又可以进行限制,从而得到更加灵活、更加适用的可重用模块,大大缩短程序的开发时间。本讲稿第六十九页,共一百七十一页3.3 继承和派生3.3.2 单继承单继承1.定义派生类定义派生类在基类的基础上定义其派生类的定义形式为:class派生类名:访问方式基类名派生类中的新成员其中:(1)派生类名由用户自己命名;(2)访问方式即继承方式,可以为public或private,默认为private方式。访问方式为public方式时,这种继承称为公有继承,而访问方式为private方式时,称为私有继承;(3)基类名必须是程序中一个已有的类。本讲稿第七十页,共一百七十一页3.3 继承和派生(4)在冒号“:”后的部分告诉系统,这个派生类是从哪个基类派生的,以及在派生时的继承方式。(5)大括号内的部分是派生类中新定义的成员。2基类与派生类之间的关系基类与派生类之间的关系(1)派生类不仅拥有属于自己的数据成员与成员函数,还保持了从基类继承来的数据成员与成员函数;同时派生类可对一些继承来的函数重新定义,以适应新的要求。(2)C+关于类的继承方式的规定,如下表3.1所示:按private方式继承(即私有继承)时,基类中的公有成员和保护成员在派生类中皆变为私有成员。按public方式继承(即公有继承)时,基类中的公有成员和保护成员在派生类中不变。本讲稿第七十一页,共一百七十一页3.3 继承和派生无论哪种继承方式,基类的私有成员均不能继承。这与私有成员的定义是一致的,符合数据封装的思想。在公有继承方式下,基类的公有成员和保护成员被继承为派生类成员时,基访问属性不变。注意:私有成员与不可访问成员是两个不同的概念。某个类的私有成员只能被该类的成员函数所访问,而类的不可访问成员甚至不能被该类自身的成员函数所访问。类的不可访问成员总是从某个基类派生来的,它要么是基类的私有成员,要么是基类的不可访问成员。基类公有派生类私有派生类public成员public成员private成员protected成员protected成员private成员private成员无法继承无法继承本讲稿第七