Upload
raiden
View
37
Download
0
Embed Size (px)
DESCRIPTION
第九章 继承性. 继承( inheritance ) 机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。 这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。 体现了由简单到复杂的认识过程 。. 9.1 基类和派生类 9.2 单一继承 9.3 多重继承 小结与作业. 9.1 继承性 9.1.1 继承和派生的概念. 层次概念 是计算机的重要概念。通过 继承 ( inheritance )的机制可对类( class )分层,提供类型 / 子类型的关系。 - PowerPoint PPT Presentation
Citation preview
1
继承( inheritance )机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。
第九章 继承性
9.1 基类和派生类
9.2 单一继承9.3 多重继承
小结与作业
2
9.1 继承性9.1.1 继承和派生的概念
层次概念是计算机的重要概念。通过继承( inheritance )的机制可对类( class )分层,提供类型 / 子类型的关系。
C++ 通过类派生( class derivation )的机制来支持继承。被继承的类称为基类( base class )或超类( superclass ),新产生的类为派生类( derived class )或子类( subclass )。
基类和派生类的集合称作类继承层次结构( hierarchy )。
如果基类和派生类共享相同的公有接口,则派生类被称作基类的子类型( subtype )。
派生反映了事物之间的联系,事物的共性与个性之间的关系。 派生与独立设计若干相关的类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程。
3
单一继承: 若某派生类只有一个基类,则称该派生类为单一继承的派生类。如由“汽车”类派生出的“小汽车”类、“大卡车”类均是单一继承。多重继承: 若某派生类有 2 个或更多个基类,则称该派生类为多重继承的派生类。“儿子”有“父亲”、“母亲” 2 个基类,他既继承了“父亲”的特性,又继承了“母亲”的特性,属于多重继承。 为了便于理解,我们用家族的名词来描述基类与派生类,一个基类得出有多个派生类,称为“一父多子”,属单一继承;一个派生类由多个基类派生,称为“一子多父”,属多重继承。 对于多层派生来说,一个派生类的基类为直接基类,而基类的基类为间接基类。
9.1.2 单一继承和多重继承
4
基 类1
基 类2
…… 基 类n
派生类 1 派生类 2
基类
派生类 1 派生类 2
( a )多重继承(一子多父)
( b )单一继承(一父多子)
一个基类可以直接派生出多个派生类
派生类可以由多个基类共同派生出来,称多重继承。
9.1.2 单一继承和多重继承
单一继承和多重继承示意图:
5
9.1.3 派生类的定义格式
由基类派生出派生类的定义的一般形式为:class 派生类名 : 访问限定符 基类名 1, 访问限定符 基类名 2,……{ private: 成员表 1 ; // 派生类增加或替代的私有成员 public: 成员表 2 ; // 派生类增加或替代的公有成员 protected: 成员表 3 ; // 派生类增加或替代的保护成员} ; // 分号不可少
其中基类 1 ,基类 2 ,……是已声明的类。只有一个基类时,是单一继承,上述给出的形式是多重继承。 在派生类定义的类体中给出的成员称为派生类成员,它们可以是新增加成员,它们给派生类添加了不同于基类的新的属性和功能,当派生类成员名与基类成员名相同时,派生类成员将取代基类成员,成为更新成员。
6
编制派生类时可分四步
吸收基类的成员
改造基类成员
发展新成员
重写构造函数与析构函数
9.1.3 派生类的定义格式
不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收
声明一个和某基类成员同名的新成员 , 派生类中的新成员就屏蔽了基类同名成员称为同名覆盖( override )
派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。
7
9.1.3 派生类的定义格式
上面的步骤就是继承与派生编程的规范化步骤。第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。第三步中,独有的新成员才是继承与派生的核心特征。第四步是重写构造函数与析构函数,派生类不继承这两种函数。不管原来的函数是否可用,必须一律重写可免出错。
9.1.4 基类成员在派生类中的访问权限 访问控制,亦称为继承方式,是对基类成员进一步的限制。访问控制也是三种:公有( public )方式,亦称公有继承保护( protected )方式,亦称保护继承私有( private )方式, 亦称私有继承。
8
9.1.4 基类成员在派生类中的访问权限
访问限定符有两方面含义:派生类成员(新增成员)函数对基类(继承来的)成员的访问(调用和操作),和从派生类对象之外对派生类对象中的基类成员的访问。
公有派生是绝对主流。
不可直接访问 protected protected
不可直接访问 privateprivate
不可直接访问 protectedpublic publicprivateprotected
privateprotectedpublic 基类权限
继承方式
9
【例 9.1 】分析程序,指出访问权限的错误。
class Base{public:int b1;protected:int b2;private:int b3;};class D1:public Base{public:void fun( ){b1=11;b2=12;b3=13;}};class D2:private Base{public:void fun( ){b1=21;b2=22;b3=23;}};class D3:protected Base{public:void fun( ){b1=31;b2=32;b3=33;}};class D11:public D1{public:void fun( ){b1=111;b2=112;b3=113;}};class D22:public D2{public:void fun( ){b1=221;b2=222;b3=223;}};class D33:public D3{public:void fun( ){b1=331;b2=332;b3=333;}};void main( ){ D11 d1;d1.b1=1;d1.b2=2;d1.b3=3; D22 d2;d2.b1=4;d2.b2=5;d2.b3=6; D33 d3;d3.b1=7;d3.b2=8;d3.b3=9;}上述程序中有 16 处错误 ! 注意,类的公有成员函数可调用类中的私有
成员,但不可调用基类中的私有成员。使用对象只能调用本类中的公有成员。
10
9.2 单一继承9.2.1 单一继承程序举例
【例 9.2 】公有继承例子#include<iostream.h>class A // 类 A 有哪些成员?{public:void fun1( );protected:int a1;private:int a2;};class B:public A // 类 B 有哪些成员?{public:void fun2( );protected:int b1;private:int b2;};class C:public B // 类 C 有哪些成员?{public:void fun3( );};.........void main( ){ C c;...........}注意程序中对象 c 有哪些可访问成员?
可访问 fun1() 和 fun2()
11
【例 9.3 】私有单一继承程序举例。#include<iostream.h>
class A{public:void fa(int i){cout<<i<<endl;}
void ga( ){cout<<"A.\n";} };
class B:A{public:void hb( ){ cout<<"B.\n";}
A::fa; };
//B 继承 A ,用 class 说明,不指明权限的一定是私有void main( )
{ B b;b.fa(5);b.ga();b.hb( ); }// 红色表示的,有错误。私有继承将基类的公有、保护成员变为私有,成员函数
可访问这些成员,但对象不可访问;间接私有继承(二次私有继承)后,原来的成员都不可访问。
12
【例 9.4 】保护单一继承程序举例。#include<iostream.h>#include<string.h>class A{public:A(const char*name1){strcpy(name,name1);} protected:char name[80]; };class B:protected A{public:B(const char*nm):A(nm){ } // 派生类的构造函数 void Print( ){cout<<"name:"<<name<<endl;}};void main( ){B b("wang");b.Print( );} 保护继承将基类的公有、保护变为保护,成员函数可访问,
但对象不可访问;间接保护继承(二次保护继承)后,仍是这样。
输出结果:
name:wang
13
派生类的构造函数不能从基类中继承,需重新定义,形式为:派生类名 :: 构造函数名(参数表) : 成员初始化表{ ……// 函数体中主要对派生类新增成员进行初始化}成员初始化列表包括:基类构造函数、子对象的构造函数、常成员初始化、其他初始化项等,执行顺序为:基类构造函数、子对象的构造函数、其他初始化项、派生类构造函数体。
在构造函数的声明中,冒号及冒号以后部分必须略去。 析构函数也不能继承,派生类的析构函数将调用基类的析构函数,执行顺序与构造函数的执行顺序相反:派生类析构函数、子对象的析构函数、基类析构函数。
9.2.2 单一继承派生类的构造函数与析构函数
14
9.2.2 单一继承派生类的构造函数与析构函数
* 在派生类构造函数中,只要基类不是使用缺省构造函数都要显式给出基类名和参数表。如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的缺省构造函数。如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数。
15
9.2.2 单一继承派生类的构造函数与析构函数
析构函数的功能是作善后工作的。 只要在函数体内把派生类新增一般成员处理好就可以了,而对新增的成员对象和基类的善后工作,系统会自己调用成员对象和基类的析构函数来完成。 析构函数各部分执行次序与构造函数相反,首先对派生类新增一般成员析构,然后对新增对象成员析构,最后对基类成员析构。
16
【例 9.5 】分析程序,掌握派生类构造函数及析构函数的用法。
#include<iostream.h>class A{ public:A( ){a=0;cout<<" 隐含构造函数。 A\n";} A(int i){a=i;cout<<" 构造函数。 A\n";} ~A(){cout<<" 析构函数。 A\n";} void Print( ){cout<<a<<',';} int Geta( ){return a;} private:int a;};class B:public A{ public:B(){b=0;cout<<" 隐含构造函数。 B\n";} B(int i,int j,int k); ~B(){cout<<" 析构函数。 B\n";} void Print(){A::Print();cout<<b<<','<<aa.Geta()<<endl;} private:int b; A aa;};B::B(int i,int j,int k):A(i),aa(j),b(k){cout<<" 构造函数。 B\n";}void main(){ B bb[2];bb[0]=B(8,3,9);bb[1]=B(17,-18,19); for(int i=0;i<2;i++)bb[i].Print();}
17
输出结果:隐含构造函数。 A
隐含构造函数。 A
隐含构造函数。 B
生成并初始化
对象 bb[0]
隐含构造函数。 A
隐含构造函数。 A
隐含构造函数。 B
生成并初始化
对象 bb[1]
构造函数。 A
构造函数。 A
构造函数。 B
生成并初始化
临时对象 B(8,3,9)
析构函数。 B
析构函数。 A
析构函数。 A
释放
临时对象 B(8,3,9)
构造函数。 A
构造函数。 A
构造函数。 B
生成并初始化
临时对象 B(17,-18,19)
析构函数。 B
析构函数。 A
析构函数。 A
释放
临时对象 B(17,-18,19)
8,9,3
17,19,-18执行 for循环
析构函数。 B
析构函数。 A
析构函数。 A
释放
对象 bb[1]
析构函数。 B
析构函数。 A
析构函数。 A
释放
对象 bb[0]
18
【例 9.6 】运行程序,观察结果,掌握派生类构造函数的用法。
#include<iostream.h>class A{ public:A( ){a=0;cout<<" 隐含构造函数。 A\n";} A(int i){a=i;cout<<" 构造函数。 A\n";} ~A(){cout<<" 析构函数。 A\n";} void Print( ){cout<<a<<',';} private:int a;};class B:public A{ public:B(){b1=b2=0;cout<<" 隐含构造函数。 B\n";} B(int i){b1=0;b2=i;cout<<" 单参数构造函数。 B\n";} B(int i,int j,int k):A(i),b1(j),b2(k){cout<<" 三参数构造函数。 B\n";} ~B(){cout<<" 析构函数。 B\n";} void Print(){A::Print();cout<<b1<<','<<b2<<endl;} private:int b1,b2; };void main(){ B b1,b2(15),b3(11,12,13); b1.Print(); b2.Print(); b3.Print();}
19
输出结果:隐含构造函数。 A
隐含构造函数。 B生成并初始化
对象 b1
隐含构造函数。 A
单参数构造函数。 B生成并初始化
对象 b2
构造函数。 A
三参数构造函数。 B生成并初始化
对象 b3
0,0,0
0,0,15
11,12,13
分别执行b1.Print(); b2.Print(); b3.Print();
析构函数。 B
析构函数。 A释放对象 b3
析构函数。 B
析构函数。 A释放对象 b2
析构函数。 B
析构函数。 A释放对象 b1
20
【例 9.7 】运行程序,观察结果,掌握派生类构造函数的特点。
#include<iostream.h>class B{ public:B(){b1=b2=0; cout<<" 隐含构造函数。 B\n";} B(int i,int j){b1=i;b2=j;cout<<" 构造函数。 B\n";} ~B(){cout<<" 析构函数。 B\n";} void Print( ){cout<<b1<<','<<b2<<endl;} private:int b1,b2;};class D:public B{ public:D(){d=0;cout<<" 隐含构造函数。 D\n";} D(int,int,int,int,int); ~D(){cout<<" 析构函数。 D\n";} void Print(){B::Print();bb.Print();cout<<d<<endl;} private:int d; B bb; };D::D(int i,int j,int k,int l,int m):B(i,j),bb(k,l),d(m){ cout<<" 五元构造函数。 D\n"; }void main(){ D d1(11,12,13,14,15),d2; d1.Print(); d2.Print(); }
21
输出结果:
构造函数。 B
构造函数。 B
五元构造函数。 D
生成并初始化
对象 d1
隐含构造函数。 B
隐含构造函数。 B
隐含构造函数。 D
生成并初始化
对象 d2
11,12
13,14
15
执行d1.Print();
0,0
0,0
0
执行d2.Print();
析构函数。 D
析构函数。 B
析构函数。 B
释放
对象 d2
析构函数。 D
析构函数。 B
析构函数。 B
释放
对象 d1
22
【例 9.8 】分析程序结果,掌握基类和派生类同名函数的用法。
#include<iostream.h>class M{ public:void Set(int i,int j){m1=i; m2=j;} void Print(){cout<<m1<<','<<m2<<endl; } protected:int m1,m2; };class N:public M{ public:void Set( ){n=m1*m2; } void Print(){cout<<n<<endl; } protected:int n; };void main(){ M m; m.Set(2,3); m.Print( ); N n; n.M::Set(5,8); n.M::Print( ); n.Set( ); n.Print( ); }
注意,同名函数使用时的区别!
输出结果:
2,3
5,8
40
23
9.2.3 子类型和赋值兼容规则
1. 子类型和类型适应B 类具有 A 类的行为(使用成员函数对成员进行操
作),则称 B 类是 A 类子类型。若 B 类是 A 类的子类型,则称 B 类型适应于 A 类型,
且 B 类的对象可以当作 A 类型的对象来使用。“ 白马”和“马”均是抽象的概念(类或类型),一
匹具体的马就是“马”这个类的一个对象。显然,“白马”是“马”的子类型,“白马”适应于“马”,一匹具体的“白马”可以当作一匹具体的“马”来使用。
一般来说,公有继承的派生类是其基类的子类型。子类型不具有对称性,即若 B 类是 A 类的子类型,则
A 类不一定是 B 类的子类型。
24
9.2.3 子类型和赋值兼容规则
2. 公有继承和赋值兼容规则赋值兼容是指在一定条件下,不同类的对象可以
赋值。有以下几种情况:① 子类型对象可以赋值给其父类型对象,反之不
成立。不过父类对象接受的仅仅是子类继承的父类中的成员,对于子类中新增加的或更新的成员是不能接受的,下述两种情况也一样。
② 父类型对象可以引用子类型对象,反之不成立。
③ 父类型对象指针可以指向子类型对象,反之不成立。
25
【例 9.9 】赋值兼容的例子。#include<iostream.h>class M{ public:M( ){m=0;} M(int i){m=i;} void Print( ){cout<<m<<endl;} int Getm( ){return m;} private:int m; };class N:public M{ public:N( ){n=0;} N(int i,int j):M(i),n(j){ } void Print(){cout<<n<<','; M::Print( ); } private:int n; };void fun(M&p){cout<<p.Getm( )-5<<','; p.Print( ); }void main(){ M m(17),q; N n(13,18); n.Print( ); q=n; q.Print( ); M *pm=new M(16); N*pn=new N(15,19); pm=pn; pm->Print( ); fun(*pn); N n1(21,34); M &rm=n1;n1.Print( );}
注意,不同对象(指针)之间的赋值!
26
【例 9.10 】分析程序结果,掌握赋值兼容和保护成员的使用。
#include<iostream.h>class B{ public:void Print(){cout<<b<<endl; } protected:void Setb(int i){b=i*i; } private:int b; };class D1:public B{ public:void Setd1(int i){d1=i;Setb(d1); } void Print(){cout<<d1<<endl; } private:int d1; };class D2:public B{ public:void Setd2(int i){d2=i;Setb(d2); } void Print(){cout<<d2<<endl; } private:int d2; };void main(){ B b; D1 d1; D2 d2; d1.Setd1(7); d1.Print( ); d2.Setd2(5); d2.Print( ); b=d1; b.Print( ); B &rb=d2; rb.Print( ); } 注意,主程序中决不能增加 b.Setb(2);!对一个类来说,保护与私有是一样
的!
27
9.3 多重继承
如有以下定义:class B1{...}; class B2{...}; class B3{...};class D:public B1, public B2, public B3{...};类 D就是由类 B1 、类 B2 、类 B3 公有派生
的派生类,或者说,类 D多重继承类 B1 、B2 、 B3
28
9.3.1 多重继承派生类的构造函数和析构函数
多重继承派生类的构造函数、析构函数不能直接继承,构造函数的定义格式为:
派生类构造函数名 ( 参数表 ): 成员初始化列表{ 派生类构造函数体 }成员初始化列表包括各个基类构造函数、子对象的类构
造函数、其他初始化项。构造函数执行时,首先按照派生类定义时的基类顺序依
次执行所有的基类构造函数,其次执行子对象类的构造函数,最后执行派生类构造函数的函数体。
派生类的析构函数中包含有其所有基类的析构函数,析构函数的执行顺序与构造函数正好相反。没有定义构造函数和析构函数时,系统提供默认的。
29
【例 9.11 】多重继承派生类定义的例子
#include<iostream.h>class B1{public:void Setb1(int i){b1=i;}void Printb1(){cout<<"b1="<<b1<<endl;} private:int b1; };class B2{public:void Setb2(int i){b2=i;} void Printb2(){cout<<"b2="<<b2<<endl;} private:int b2; };class D:public B1,B2 //因为用 class 定义, B2 的继承权限隐含私有{public:void Setd(int i,int j){d=i;Setb2(j);} void Printd(){Printb2();cout<<"d="<<d<<endl;} private:int d; };void main(){D objd; objd.Setb1(16); objd.Printb1( ); objd.Setd(24,76); objd.Printd( );}注意,类 D 有公有成员函数: Setb1 、 Printb1 、 Setd 、 Printd 有私有成员函数: Setb2 、 Printb2 ,私有数据成员: d请问:通过类 D 的对象是否可以操作类 B1 、 B2 的私有数据成员?
30
【例 9.12 】多重继承派生类的构造函数及不同基类中含有同名函数的例子
#include<iostream.h>class B1{public:B1(int i){b1=i;} int Getd( ){return b1;} protected:int b1;};class B2{public:B2(char i){b2=i;} char Getd( ){return b2;} protected:char b2;};class D:public B1,public B2 {public:D(int,char,double); double Getd( ){return d;} private:double d;};D::D(int i,char j,double k):B1(i),B2(j){d=k;}void main( ){B1 b1(18); B2 b2('G'); D d(15,'H',6.56); cout<<"b1="<<b1.Getd()<<"\nb2="<<b2.Getd()<<"\n\nD:b1="; cout<<d.B1::Getd()<<"\nDb2="<<d.B2::Getd()<<"\nD:d="<<d.Getd()<<endl; B1 *ptrb1=&d; B2 rb2=d; // 兼容赋值 cout<<"\nb1="<<ptrb1->Getd()<<"\nb2="<<rb2.Getd()<<endl; }注意,类 D 有 4 个公有成员函数: Getd( 继承 B1) 、 Getd( 继承 B2) 、 G
etd ( 新增加 ) 、 D( 构造函数 ) 有 2 个保护数据成员: b1 、 b2 。有 1 个私有数据成员: d
31
【例 9.13 】多重继承派生类构造函数、析构函数执行顺序验证
#include<iostream.h>class B1{public:B1(int i){b1=i;cout<<" 构造 B1.b1="<<b1<<endl;} void Print(){cout<<b1<<endl;} ~B1(){cout<<" 析构 B1.b1="<<b1<<endl;} private:int b1; };class B2{public:B2(int i){b2=i; cout<<" 构造 B2.b2="<<b2<<endl;} void Print(){cout<<b2<<endl;} ~B2(){cout<<" 析构 B2.b2="<<b2<<endl;} private:int b2; };class B3{public:B3(int i){b3=i; cout<<" 构造 B3.b3="<<b3<<endl;} void Print(){cout<<b3<<endl;} ~B3(){cout<<" 析构 B3.b3="<<b3<<endl;} private:int b3; };
32
【例 9.13 】多重继承派生类构造函数、析构函数执行顺序验证
class B4{public:B4(int i){b4=i; cout<<" 构造 B4.b4="<<b4<<endl;} void Print(){cout<<b4<<endl;} ~B4(){cout<<" 析构 B4.b4="<<b4<<endl;} private:int b4; };class D:public B3,public B1,public B4{public: D(int d1,int d2,int d3,int d4,int d5):B1(d1),B3(d3),B4(d4),b2(d5) {d=d2;} void Print() {B1::Print();cout<<d<<endl;B3::Print();B4::Print();b2.B2::Print();} private:int d; B2 b2;};void main(){ D d(11,12,13,14,15);d.Print(); }注意:构造函数的执行顺序是按照派生类定义的顺序进行的。
33
9.3.2 多重继承的二义性程序设计语言严防语法的二义性,即同一个语句或语法单元可能出
现不同的含义,称为二义性。实际上,计算机在翻译时是不会出现二义性的,二义性实际是程序员在编程时理解上的二义性。多重继承中理解上容易产生二义性的有以下两种情况:
1. 调用不同基类中的相同成员名2. 调用共同基类的成员一个派生类有多个直接基类,而这多个基类又是由一个基类派生的,
即派生类的多个父类拥有共同的父类(相对于派生类来说,应称为祖父类)。如类 A 有父类 B 、 C 、 D ,而 B 、 C 是类 E 的子类,则类 E 是类 A 的共同基类。
解决情况 1. 的方法是在成员名前增加类名及作用域运算符,如类 A的基类 B 、 C 中均有成员 f 在 A 中可被调用,调用 B 中 f 时,应写为 B::f ,调用 C 中 f 时,写成 C::f 。
解决情况 2. 的方法有两种措施,其一与上述方法相同,即成员限定法,在 A 中调用 E 中的成员 h 时,不写 E::h,而写成 B::h 或 C::h,因为 A中使用的 h 是通过 B 或 C 间接继承来的。
其二是采用虚基类 ( 后面介绍)。
34
【例 9.14 】解决不同基类中同名成员二义性的例子
#include<iostream.h>class A {public:void f( ){cout<<"A::f( ).\n";}};class B {public:void f( ){cout<<"B::f( ).\n";} void g( ){cout<<"B::g( ).\n";}};class C:public A,public B {public:void g( ){cout<<"C::g( ).\n";} void h1( ){A::f( );cout<<"C::h1( ).\n";} void h2( ){B::f( );cout<<"C::h2( ).\n";}};void main( ){ C c; c.A::f( ); c.B::f( ); c.B::g( ); c.g( ); c.h1( ); c.h2( ); }注意,类 C 有公有成员函数: f(从 A 继承 ) 、 f(从 B 继承 ) 、 g
(从 B 继承 ) 、 g(自身新建 ) 、 h1 (自身新建 ) 、 h2 (自身新建 )等 6 个,同名成员必须区别,继承来的要注明成员的定义范围,如果不指明范围,一定是派生类中新定义的,否则编译将出错。
35
【例 9.15 】解决共同基类产生二义性的例子
#include<iostream.h>class A{public:A(int i){a=i;cout<<" 构造 A\n";} void Print(){cout<<a<<',';} ~A(){cout<<" 析构 A\n";} private:int a;};class B1:public A{public:B1(int i,int j):A(i){b1=j;cout<<" 构造 B1\n";} void Print(){A::Print();cout<<b1<<',';} ~B1(){cout<<" 析构 B1\n";} private:int b1; };class B2:public A{public:B2(int i,int j):A(i){b2=j;cout<<" 构造 B2\n";} void Print(){A::Print();cout<<b2<<',';} ~B2(){cout<<" 析构 B2\n";} private:int b2; };class C:public B1,public B2{public:C(int i,int j,int k,int l,int m):B1(i,j),B2(k,l){c=m;cout<<" 构造 C\n";} void Print(){B1::Print();B2::Print();cout<<c<<endl;} ~C(){cout<<" 析构 C\n";} private:int c; };void main( ){ C c(16,19,23,25,38);c.Print();} 注意,类 C 的公有成员函数 Print 有 5 个,从 A 中间接继承来 2 个,一个由 B1继承来,一个由 B2 继承来。若增加语句 c.A::Print( ); 则出现歧义错误。另外,从运行结果可以看出,类 A 的构造函数被调用 2 次。
36
【例 9.16 】成员限定法选择不同基类中的同名函数,解决二义性的例子
#include<iostream.h>class A{ public:char *Return( ){return "A.\n"; } };class B:public A{ public:char *Return( ){return "B.\n"; } };class C:public B{ public:char *Return( ){return "C.\n"; } };class D:public C{ public:char *Return( ){return "D.\n"; } };void main(){ D d; cout<<d.A::Return( ); cout<<d.B::Return( ); cout<<d.C::Return( ); cout<<d.Return( );}注意, Return 函数的具体表示方法。
37
9.3.3 虚基类
将公共基类说明成虚基类,可以避免派生类构造时,公共基类的构造函数被多次调用的情况,而且在初始化派生类对象时内存中只保存一份公共基类的成员。
说明虚基类的方法是: class 派生类名 virtual 继承方式 虚基类名 使用虚基类的派生类的构造函数中成员初始化列表中应专门
列出虚基类的构造函数: 派生类构造函数名 ( 参数表 ): 若干基类构造函数,子对象
构造函数,虚基类构造函数 { 派生类构造函数体 }只在创建派生类对象时调用一次虚基类构造函数,而在基类
构造函数中不再调用虚基类构造函数,用以保证虚基类成员只初始化一次。
38
【例 9.17 】在【例 9.15 】中使用虚基类解决共同基类二义性
#include<iostream.h>class A{public:A(int i){a=i;cout<<" 构造 A\n";} void Print( ){cout<<a<<',';} ~A( ){cout<<" 析构 A\n";} private:int a;};class B1:virtual public A{public:B1(int i,int j):A(i){b1=j;cout<<" 构造 B1\n";} void Print( ){A::Print( );cout<<b1<<',';} ~B1( ){cout<<" 析构 B1\n";} private:int b1; };class B2:virtual public A{public:B2(int i,int j):A(i){b2=j;cout<<" 构造 B2\n";} void Print( ){A::Print( );cout<<b2<<',';} ~B2( ){cout<<" 析构 B2\n";} private:int b2; };
39
【例 9.17 】在【例 9.15 】中使用虚基类解决共同基类二义性
class C:public B1,public B2{public:C(int i,int j,int k,int l,int m):B1(i,j),B2(k,l),A(i) { c=m;cout<<" 构造 C\n"; } void Print( ) {B1::Print( );B2::Print( );cout<<c<<endl;} ~C( ){cout<<" 析构 C\n";} private:int c; };void main( ){ C c(16,19,23,25,38);c.Print( );c.A::Print( );}
注意,与例 15比较类 C 的公有成员函数 Print 只有 4 个,从A 中间接继承来的只有 1 个,即由 B1 和 B2 继承来的是一个。若增加语句 c.A::Print( ); 则不会出现歧义错误。另外,从运行结果可以看出,类 A 的构造函数被调用 1 次。
40
【例 9.18 】运行程序,分析结果,注意虚基类的作用
#include<iostream.h>class A{public:A(const char*s){cout<<s<<endl;} };class B:virtual public A{public:B(const char*s1,const char*s2):A(s1) {cout<<s2<<endl;} }; class C:virtual public A{public:C(const char*s1,const char*s2):A(s1) {cout<<s2<<endl;} }; class D:public B,public C{public: D(const char*s1,const char*s2, const char*s3,const char*s4): B(s1,s2),C(s1,s3),A(s1){ cout<<s4<<endl;} };void main( ){ D *ptr=new D("class A","class B","class C","class D"); delete ptr;}注意,构造函数的执行顺序为: 虚基类 A 、基类 B 、基类 C 、派生类 D
41
9.3.4 多重继承应用举例 【例 9.19 】计算 4 类人员月收入的程序 面向对象设计过程: 一、设计类(对象) 1.顶层类(对象):职工(编号,姓名,薪金,数据初始化
方法) 2. 、二层类(对象):技术员(小时工资)、总经理(固定工资)、销售员(销售额提成)
3. 、三层类(对象):销售经理(固定工资+销售额提成) 每个类(对象)均有数据初始化方法、计算薪金方法、显示
数据方法等(公有成员函数) 二、研究类(对象)的层次关系 显然,二层类(对象)可以从“职工”类(对象)继承许多信息,“销售经理”又可以从二层类(对象)中的“总经理”和“销售员”中继承,这样一来,相对于“销售经理”来说,“职工”是公共基类,可以设计成虚基类。
42
【例 9.19 】源程序 ( 定义“职工”类 )
#include<iostream.h>class Employee{public: Employee( ) {cout<<"编号: ";cin>>no; cout<<"姓名: ";cin>>name; salary=0;} protected: int no; char name[20]; double salary;};
43
【例 9.19 】源程序 ( 定义“技术员”类 )
class Technician:public Employee{public: Technician( ){hourlyrate=150;} void pay( ) {cout<<"\n技术员: "<<name<<" 本月工作时数: "; cin>>workhours; salary=hourlyrate*workhours;} void display( ) {cout<<"技术员 :"<<name<<" 编号 :"<<no; cout<<" 月薪 :"<<salary<<endl; } private: double hourlyrate; int workhours;};
44
【例 9.19 】源程序 ( 定义“销售员”类 )
class Salesman:virtual public Employee{public: Salesman ( ){commrate=0.07;} void pay( ) {cout<<"\n销售员: "<<name<<"月销售量 :"; cin>>sales; salary=sales*commrate;} void display( ) { cout<<"销售员 :"<<name<<"编号 :"; cout<<no<<"月薪 :"<<salary<<endl; } protected: double commrate,sales;};
45
【例 9.19 】源程序 ( 定义“总经理”类 )
class Manager:virtual public Employee{public: Manager( ){ monthlypay=8500; } void pay( ){ salary=monthlypay; } void display( ) {cout<<"经理 :"<<name<<" 编号 :"<<no; cout<<" 月薪 :"<<salary<<endl; } protected: double monthlypay;};
46
【例 9.19 】源程序 ( 定义“销售经理”类 )
class Salesmanager:public Manager,public Salesman{public: Salesmanager( ) { monthlypay=4000; commrate=0.005; } void pay( ) { cout<<"\n销售经理 :"<<name<<"部门月销售量 :"; cin>>sales; salary=monthlypay+sales*commrate;} void display( ) {cout<<"销售经理 :"<<name<<" 编号 :"<<no; cout<<" 月薪 :"<<salary<<endl; }};
47
【例 9.19 】源程序 ( 定义主函数 )void main( ){ cout<<"创建经理信息 :\n"; Manager m1; cout<<"创建技术员信息 :\n"; Technician t1; cout<<"创建销售员信息 :\n"; Salesman s1; cout<<"创建销售经理信息 :\n"; Salesmanager sm1; m1.pay( ); m1.display( ); t1.pay( ); t1.display( ); s1.pay( ); s1.display( ); sm1.pay( ); sm1.display( );} 观察本程序运行结果,体会面向对象编程的思想
48
【例 9.20 】计算日期和时间的程序
面向对象设计过程: 一、设计类(对象) 1.“日期”类(年、月、日等数据,设置日期、输出日期等方法)
2.“ 时间”类(时、分、秒等数据,设置时间、输出时间等方法)
3.“ 时间日期”类,有时间和日期的所有数据,并增加日期和时间的联合输出方法
二、研究类(对象)的层次关系 显然,“时间日期”类,可由“日期”、“时
间”两个类多重继承得来。
49
【例 9.20 】源程序 ( 定义“日期”类 )#include<iostream.h>#include<stdio.h>typedef char string80[80];class Date{public: Date( ){ } Date(int y,int m,int d){ SetDate(y,m,d); } void SetDate(int y,int m,int d) { Year=y;Month=m;Day=d; } void GetStringDate(string80 &Date)//此处 Date 不是类名 , 是字符串
名 { sprintf(Date,"%d,%d,%d",Year,Month,Day); // 上述函数将年月日的具体数据作为字符串输出到字符数组 Date 中 } protected: int Year,Month,Day;};
50
【例 9.20 】源程序 ( 定义“时间”类 )
class Time
{public:
Time( ){ }
Time(int h,int m,int s){ SetTime(h,m,s); }
void SetTime(int h,int m,int s)
{ Hours=h;Minutes=m;Seconds=s; }
void GetStringTime(string80 &Time)
{ sprintf(Time,"%d,%d,%d",Hours,Minutes,Seconds); }
protected:
int Hours,Minutes,Seconds;
};
51
【例 9.20 】源程序 ( 定义“时间日期”类 )
class TimeDate:public Date,public Time{public: TimeDate( ){ } TimeDate(int y,int mo,int d,int h,int mi,int s): Date(y,mo,d),Time(h,mi,s) { } void GetStringDT(string80 &DTstr)
{ sprintf(DTstr,"%d/%d/%d;%d:%d:%d",Year,Month, Day,Hours,Minutes,Seconds);
}
};
52
【例 9.20 】源程序 ( 定义主函数 )void main( ){ TimeDate date1,date2(2003,2,26,9,11,30); string80 DemoStr; // 定义了一个字符数组,即字符串 date1.SetDate(2000,3,24); date1.SetTime(12,45,15); date1.GetStringDT(DemoStr); cout<<"The date1:"<<DemoStr<<endl; date1.GetStringDate(DemoStr); cout<<"The date1:"<<DemoStr<<endl; date1.GetStringTime(DemoStr); cout<<"The date1:"<<DemoStr<<endl; date1.GetStringDT(DemoStr); cout<<"The date1:"<<DemoStr<<endl; }注意,程序中用到库 stdio.h 中的函数 sprintf(字符串 ,输出格式 ,输出项列表 )的作用是将输出内容输出到字符串中。