C++的类派生与虚拟函数.docx
- 文档编号:12673553
- 上传时间:2023-04-21
- 格式:DOCX
- 页数:49
- 大小:44.57KB
C++的类派生与虚拟函数.docx
《C++的类派生与虚拟函数.docx》由会员分享,可在线阅读,更多相关《C++的类派生与虚拟函数.docx(49页珍藏版)》请在冰豆网上搜索。
C++的类派生与虚拟函数
第六章C++的类派生与虚拟函数
第四章中已讨论的类与数据封装概念可以说是C++的第一大特征。
而本章将要论述的类派生与多态概念则是C++的另外两大特征。
由于有了前两章的基础,所以将这两个特征合并一章讨论也就不太困难了。
§1C++语言所处理的类的相互关系
§1.1包容关系的实现
一个类包含有一个以上的类作为其独立的属性成员的关系称为包容关系。
被包容的类作为包容类的一个成员当然具备其成员地位应有的各种特性,但由于被包容类自身也含有成员,所以二者间又必然存在有其相互间的关联关系。
例1:
#include
#include
classCourse
{
charname[10];
intscore;
public:
Course(){}
voidinit(char*na,intsc)
{
score=sc;
name[9]='\0';
strncpy(name,na,9);
}
intgetscore(){returnscore;}
char*getname(){returnname;}
};
classStudent
{
charname[10];
intnumber,total;
Course*p;
public:
Student(char*na,intnu):
number(nu)
{
inti,ccs;
charcna[10];
name[9]='\0';
strncpy(name,na,9);
cout<<"Howmanycoursearethestudentselected:
";
cin>>total;
p=newCourse[total];
cout<<"Pleaseinputeachcoursenameandit'sscore:
"< for(i=0;i { cout<<"Thecoursename: "; cin>>cna; cout<<"thescore: "; cin>>ccs; p[i].init(cna,ccs); } } voidoperator! () { inti; cout<<"Thestudent'snameis"< for(i=0;i { cout<<""< } } }; voidmain() { Students1("Wang",123); ! s1; } 本例中的Student类的private区包容了一个Course类的指针。 考虑到一个学生与课程的关系为1: n,所以在初始一个学生对象时临时确定该学生所选的课程数目。 考察Course类会发现其内的缺省构造函数非常简单,这便是被包容类的特征。 当一个类要包容另一个类的不确定数目的对象且其成员初始工作不能在一次构造函数的引用中完成时,被包容类通常都是以数组或链表等结构出现在包容类中的。 此时被包容类的构造函数很难能发挥太大的作用,因此其成员初始化工作只能将内存征用和初始赋值两个步骤分开进行。 前者由缺省构造函数实现,而后者自然要由其它的成员函数在以后的引用中完成了。 C++语言的包容关系具有下述特征: ·具有包容关系的两个类存在各自的独立性,即自身对象的存在不依赖于对方是否存在(与理论概念有区别); ·被包容类对象只能以一个成员的身份被定义在包容类中,因而要受到成员所在区属性的影响; ·被包容类自身的成员不会由于存在包容关系而成为包容类成员; §1.2类模板 在客观模型中的类内的成员数据和成员函数的参数或返回值类型也有不能确定类型的情形。 此时可应用类模板来取代这些未知的成分,待到运行时再动态地装入具体的类型。 类模板的声明的语法格式如下: template 与函数模板相同的格式要求,类模板的声明的语句也必须置于类声明的前面。 如有两个以上的模板参数时,应使用逗号分隔。 使用含类模板的类定义对象时也必须在类名的后面带上“<实际类型>”的参数形式。 类模板最常用于各种类包容关系的设计模型之中。 例2: #include template classArray { T*ar; public: Array(intc){ar=newT[c];} voidinit(intn,Tx){ar[n]=x;} T&operator[](intn){returnar[n];} }; voidmain() { Array cout<<"Pleaseinputeveryelement'svalue: "< for(inti=0;i<5;i++) { cout<<"No."< '; cin>>array[i]; } } 本例是在类Array内包容了一个未知类型的数组,在全局函数main()中导入的实际类型为int。 利用对算苻“[]”的重载而大大简化了数组的初始化。 读者还可向Array类内加入排序、插入、删除、交换、求平均数或输出等成员函数。 在设计模型中实际导入的类型也可以是另一个类类型。 例3: #include classA { intj; public: A(){} A(intx): j(x){} A(A*x){j=x->j;} voidoperator! (){cout<<"J="< }; template classB { inti; T*x; public: B(intxa,T*p): i(xa){x=newT(p);} voidoperator! (){cout<<"I="< *x;} }; voidmain() { Aa (1);//最后的显示结果为: ! b;//J=1 } 本例体现了类的包容关系。 在类的包容关系中常有将一组类对象装进包容类对象中再参加各种运算的需求。 例4: #include #include classStudent { intnumber; staticStudent*ip; Student*p; public: Student(){p=NULL;} Student(intn); staticStudent*get_first(){returnip;} intget_number(){returnthis->number;} Student*get_next(){returnthis->p;} }; Student: : Student(intn): number(n)//依据学号的大小顺序将学生对象插入链表 { p=NULL; if(ip==NULL)ip=this;//如果是第一个则使头指针指向该对象 else { Student*temp=ip; if(n else { while(temp) { if(n { p=temp->p;//链中间对象的插入 temp->p=this; break; } else { if(temp->p->p==NULL)//最后一个链的插入 { temp->p->p=this; break; } } temp=temp->p; } } } } Student*Student: : ip=NULL; template classClass { intnum; T*p; public: Class(){} Class(intn): num(n){p=NULL;} T*insert(intn){p=newT(n);returnp;} voidlist_all_member(T*x) { T*temp=x; while(temp) { cout< temp=temp->get_next(); } } }; voidmain() { Class x97x.insert(23); x97x.insert(12); x97x.insert(38); x97x.insert(22); x97x.insert(32); x97x.list_all_member(Student: : get_first()); } 本例是一个可包容多个学生的班级类的设计模型的实现。 被插入的学生对象按其学号自小到大构成单向的对象链表结构。 为突出插入的过程,处理链表的构造函数的定义体被移至class之外。 读者可在此基础上很容易地写出相应的删除、查询等成员函数了。 §1.3派生关系 在C++语言中一个类的的成员是由继承另一个类的部分或全部的成员与自身定义的成员所构成的结构关系称为派生关系。 将被继承的类称为基类(或父类),将继承基类后产生的类称为派生类。 继承与派生并非是完全透明的,在实现中派生类必须以适当的继承方式来对继承得到的成员实施访问。 为了保证数据封装的安全,无论何种继承方式得到的派生类都不能直接访问处在基类的private区内的任何成员。 C++语言有专门的语句来描述。 其语法格式为: class类名: [public|private]基类名{…定义体}; 定义式中若省略“public”或“private”关键字,则缺省继承方式值为private。 一.两种继承方式 格式中的“public”和“private”关键字表达了两种继承方式。 ⒈public继承方式 以此方式继承得到的派生类成员的属性与其在基类中定义的属性相同(但不包括private区)。 ⒉private继承方式 以此方式继承得到的派生类成员的属性将全部成为private属性(但不包括private区)。 ⒊类的保护属性(protected) 第四章中未曾讲述的保护属性(即protected区)成员在使用上与private属性完全一样,唯一的不同便是当发生派生后,处在基类protected区的成员可被派生类直接访问。 ⒋派生类中继承属性的变化对比 仅按如上的定义似是难以理解,把两者结合在一起对比的讨论,就会使读者容易掌握了。 表6-1列出了public继承方式和private继承方式的对比情况。 表6-1两种继承方式的对比 派生类中以public方式继承 派生类中以private方式继承 public public private protected protected private private - - 由此表可见,透明继承并不是无条件延续的。 有时派生类甚至不能访问继承到的基类的任何成员,如在下面结构的派生中,尽管类A是派生类C的基类,派生类C却无法透明访问所继承的基类A中的任何成员。 基类A ↓以private方式继承 派生类B ↓以任何方式继承 派生类C 二.对继承来的成员的访问 派生类本身定义成员无特殊规定,在使用继承来的成员时视同为自身的成员。 但要注意由于继承方式的不同而使被继承成员属性发生改变所造成的影响。 例5: #include classA { protected: intm[10],k; public: voidinit(){for(k=0;k<10;k++)m[k]=k;} voidoperator! (){for(k=0;k<10;k++)cout< }; classB: publicA { intn; }; voidmain() { Bt1; t1.init(); ! t1; } 如果将本例中classB声明中的“public”改成“private”,则编译就会对书写在main()中的ti.init()和! t1两条语句报错,原因请读者根据上述概念自己得分析出来。 三.派生对友元的影响 若基类曾声明过(类、类成员函数、函数)友元,派生类不再继承基类的友元声明。 所以那些基类的友元是不能再对派生类的对象进行访问了。 四.派生关系的特征 在做了上述讨论的基础上可以推出派生关系的下述特征: ·派生类没有独立性,意即派生类不能脱离基类而独立存在; ·派生类对其所继承的基类成员的可访问程度因继承方式的不同而不同; ·无论派生类对其所继承的基类成员能否直接访问,被继承的基类成员都是其成员; §1.4派生类的构造函数 如果留意一下上面的例子,会发现基类A中没有定义任何构造函数,这也就意昧着在派生后的派生类中的构造函数一定会有新的变化。 由于构造函数只负责对本类对象中的成员数据进行一次性的初始化,而类派生后,由于类名发生改变,继承来的构造函数及成员数据的初始化显然只能通过派生类自己的构造函数来实现了。 C++语言系统规定派生类构造函数必须在其定义体(不是声明体)中使用成员初始化表的格式来引用其上一级的基类的构造函数(当然前提是基类中含有构造函数),即有如下的声明格式: 派生类名∷派生类构造函数(派生类参数表)[: 基类名(基类参数表),…]{…} 例6: #include #include classST_COM { protected: charname[10]; unsignedintnum; floatmat,eng,phy; public: ST_COM(char*na,unsignedintn,floatma,floaten,floatph): num(n),mat(ma),eng(en),phy(ph) {strcpy(name,na);} }; classEL_DEP: publicST_COM { floatpex,elnet,dst; public: EL_DEP(char*na,unsignedintn,floatma,floaten,floatph,floatpe,floatel,floatd) : ST_COM(na,n,ma,en,ph),pex(pe),elnet(el),dst(d){;} voidoperator! () { cout<<"Name: "< "< cout<<"MatchematicsScor: "< cout<<"EnglishScor: "< cout<<"PhysicsScor: "< cout<<"ExchangeScor: "< cout<<"Elec_netScor: "< cout<<"Data_structScor: "< } }; voidmain() { EL_DEPa("wang",1234,71,72,73,81,82,83); ! a; } 本例中ST_COM类是学生公共课(数学、英语、物理)的数据结构。 EL_DEP可以代表某系所开课(程控交换、电信网络、数据结构)的数据结构。 EL_DEP类继承了ST_COM类的结构而成为其派生类。 在定义EL_DEP的类对象时随同初始化了基类的全部成员数据。 请读者将引用基类ST_COM构造函数的初始化成员形式改到EL_DEP类对象的构造函数过程体中一试,看会出现何种效果。 由于在派生类中定义构造函数受到基类构造函数的影响,所以有如下两个注意点: ①引用上的执行顺序 在引用派生类的构造函数时将优先引用声明在成员初始化表内的基类构造函数,也就是先初始由基类派生来的成员,然后再执行自身的构造函数。 即使有意把引用的基类构造函数部分写在一系列的初始化表的最后面也仍不会改变这种引用顺序。 反之,在引用析构函数时,则是派生类的析构函数引用在先,基类的析构函数引用在后了。 ②缺省引用关系 如在派生类构造函数的成员初始化表中没有指明要引用的基类构造函数,则一定会引用基类的缺省构造函数。 另一方面当然不能在派生类的成员初始化表中去无中声有地引用不存在的基类构造函数,甚至是其它属于基类的成员函数。 某些手册中称,当派生类只单一继承一个基类时,可以将成员初始代表中的基类构造函数名省略,即写成: “: (参数)”的样子。 经试,这在Microsoft的C++编译中是根本不允许的,并对此专门还有说明。 在BorlandC++编译中虽允许,但也要给出警告错误,所以建议读者编程时不要用此格式。 §1.5类派生引出的成员覆盖(Memberoverridden) 派生关系将使数据结构发生关联性的改变。 当一个已在基类中声明的成员名又被在派生类中重新声明所产生的效果便称为成员覆盖。 可在上例中的两个类中各插入一个同名的显示函数和用于记录平均成绩的成员avg来观察成员覆盖后的情形。 例7: #include #include classST_COM { protected: charname[10]; unsignedintnum; floatmat,eng,phy,avg; public: ST_COM(char*na,unsignedintn,floatma,floaten,floatph): num(n),mat(ma),eng(en),phy(ph) {strcpy(name,na);avg=(mat+eng+phy)/3;} voidoperator! () { cout<<"Name: "< "< cout<<"MatchematicsScor: "< cout<<"EnglishScor: "< cout<<"PhysicsScor: "< } }; classEL_DEP: publicST_COM { floatpex,elnet,dst,avg; public: EL_DEP(char*na,unsignedintn,floatma,floaten,floatph,floatpe,floatel,floatd) : ST_COM(na,n,ma,en,ph),pex(pe),elnet(el),dst(d){avg=((pex+elnet+dst)/3+ST_COM: : avg)/2;} voidoperator! () { cout<<"ExchangeScor: "< cout<<"Elec_netScor: "< cout<<"Data_structScor: "< cout<<"AverageScor: "< } }; voidmain() { EL_DEPa("wang",1234,71,72,73,81,82,83); ! a; } 接下来执行时所显示的结果只是派生类对象中的成员数据值。 这个例子证明了在派生类中存在着基类与派生类都拥有各自独立作用域的论点。 意即在派生类中如出现成员覆盖,则成员所在的当前作用域优先。 正是由于类作用域的影响,才使得派生更有意义。 对上例,我们只要在EL_DEP类的operator! ()函数的最前端插入“! *((ST_COM*)this);”语句即可直接引用位于基类作用域中的同名成员函数了。 归纳一下,就将上例引出的现象叫做成员覆盖,即总是派生类的成员掩盖了从基类继承得到的同名成员。 这种掩盖即不是成员的丢失,也不是成员的重载。 因为经类作用域声明后仍可引用基类的同名成员函数,而且可以如同上例那样由派生类的成员函数去引用从基类继承来的同名成员,这是重载所没有的效果。 基于这一成员覆盖的机制,可以充分体现派生的优越之处。 那就是派生类在继承基类的基础上还可以继续扩充原设计中所未考虑到的内容,从而使一个软件系统的生命周期大大地延长了,这便是可重用软件设计思想的最终目标。 §1.6多重继承 若一个派生类是在继承多个基类的情况下而得到的便称为多重继承。 相应的C++语句的格式为: class派生类名: [public|private]基类1[,public|private]基类2… 多重继承是前述单一继承的延续。 由于继承了多个基类的构造函数,使得在派生类中定义的构造函数就必须也承担对继承来的各个基类成员的初始化过程,既在派生类构造函数中应用成员初始化表分别对每个上一级基类的构造函数进行引用。 引用执行的顺序则是按照继承时声明的顺序进行了。 但是使用多重继承不当会导致出现不确定问题。 所谓不确定问题是指当在两个以上的同级基类中声明了同名成员,而在派生类中既不再声明同名成员又不应用类作用域操作符去指名访问这个同名成员时,便会出现使编译器无法区分应引用位于哪个基类中的这个同名成员的现象。 解决这种不确定问题的方法也很简单,就是要么在引用时声明类作用域操作符,要么在派生时使用将要讲述的虚拟基类的方法去间接引用基类同名成员便是了。 以下举一个综合例来做本节的小结。 例8: #include #include #include #defineX10 #defineY10 classWin_shd { char_huge*sac; staticshortnum;//对象计数器 protected: shortsxp,syp,sx_len,sy_len,scr; public: Win_shd(shortx,shorty,shortxl,shortyl,shortc): sxp(x),syp(y),sx_len(xl),sy_len(yl),scr(c) { if(num==0)//如果尚未定义对象,则初始化图形显示模式 { _setvideomode(_MAXRESMODE); _setvieworg(
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 派生 虚拟 函数