第8章多继承类 ■8.1多继承类 ■ 8.2虚基类 8.3派生类成员 ■8.4构造与析构 ■8.5类的存储空间 2
2 第8章 多继承类 8.1 多继承类 8.2 虚基类 8.3 派生类成员 8.4 构造与析构 8.5 类的存储空间
8.1多继承类 特点: ①多继承派生类有多个基类或虚基类。 ②派生类继承所有基类(包括间接基类)的数据成员和成员函数 ③ 派生类可以定义新的数据成员和函数成员,以便描述新类特 有的或不同的属性和功能 单继承是多继承的一种特例,多继承派生类具有更强的类型 表达能力。 多继承机制是C+语言所特有的(Java、SmallTalk没有) 。 因此,C++具有更强的描述对象方面的功能。其他面向对象 语言需要描述多继承类的对象时,常常通过对象成员或委托 代理实现多继承。委托代理在多数情况下能够满足需要,但 当对象成员和基类的类型相同或存在共同的基类时,就可能 对同一个物理对象重复进行初始化。【例8.1】 3
3 8.1 多继承类 特点: ① 多继承派生类有多个基类或虚基类。 ② 派生类继承所有基类(包括间接基类)的数据成员和成员函数 ③ 派生类可以定义新的数据成员和函数成员,以便描述新类特 有的或不同的属性和功能 ④ 单继承是多继承的一种特例,多继承派生类具有更强的类型 表达能力。 ⑤ 多继承机制是C++语言所特有的(Java、SmallTalk没有)。 因此,C++具有更强的描述对象方面的功能。其他面向对象 语言需要描述多继承类的对象时,常常通过对象成员或委托 代理实现多继承。委托代理在多数情况下能够满足需要,但 当对象成员和基类的类型相同或存在共同的基类时,就可能 对同一个物理对象重复进行初始化。【例8.1】
【例8.1】定义具有水平滚动条和垂直滚动条的窗口类。 class Window{ I. public: Window(int top,int left,int bottom,int right); Window(); 为 class HScrollbar . public: HScrollbar (int top,int left,int bottom,int right); ~HScrollbar () 街 class VScrollbar{ ∥ public: VScrollbar (int top,int left,int bottom,int right); ~VScrollbar(); 4
4 【例8.1】定义具有水平滚动条和垂直滚动条的窗口类。 定义具有水平滚动条和垂直滚动条的窗口类。 class Window{ //... public: Window(int top, int left, int bottom, int right); ~Window( ); }; class HScrollbar{ //... public: HScrollbar (int top, int left, int bottom, int right); ~ HScrollbar ( ); }; class VScrollbar{ //... public: VScrollbar (int top, int left, int bottom, int right); ~ VScrollbar( ); };
class Scrollable Wind:public Window HScrollbar hScrollBar;/委托hScrollBar代理水平滚动 VScrollbar vScrollBar;/委托vScrollBar代理垂直滚动 ∥. public: ScrollableWind(int top,int left,int bottom,int right); -Scrollable Wind(); 房 ScrollableWind:ScrollableWind (int t,int I,int b,int r):Window(t,L,b,r), hScrollbar(t,r+1,b-1,r),vScrollbar(b-1,1-1,b,r+1) 如果Vindow、hScrollBari和hScrollBar2分别初始化 显示端口,则派生类Scrollable Wind的对象就会多次 初始化显示端口,从而导致显示屏因多次初始化显示 端口出现多次闪烁。→使用虚基类定义Scrollable Wind 5
5 class ScrollableWind: public Window{ HScrollbar hScrollBar; //委托hScrollBar代理水平滚动 VScrollbar vScrollBar; //委托vScrollBar代理垂直滚动 //... public: ScrollableWind(int top, int left, int bottom, int right); ~ScrollableWind( ); }; ScrollableWind::ScrollableWind (int t, int l, int b, int r):Window(t, l, b, r), hScrollbar(t, r+1, b-1, r), vScrollbar(b-1,l-1,b,r+1) { //... } 如果Window 、hScrollBar 和hScrollBar分别初始化 显示端口,则派生类ScrollableWind的对象就会多次 初始化显示端口,从而导致显示屏因多次初始化显示 端口出现多次闪烁。 Æ使用虚基类定义ScrollableWind
用多继承方式定义派生类Scrollable Wind class Scrollable Wind:public Window,public HScrollbar,public VScrollbar{ l public: Scrollable Wind(int top,int left,int bottom,int right); -Scrollable Wind(); }; ScrollableWind:Scrollable Wind (int t,int I,int b,int r):Window(t,I,b,r), HScrollbar(t,r+1,b-1,r),VScrollbar(b-1,1-1,b,r+1) .. 1.多继承派生类的定义: class Derived::基类1,基类2,.{ public }; private protected 2.派生类对象多次初始化同一基类数据成员问题。 6
6 用多继承方式定义派生类 用多继承方式定义派生类ScrollableWind ScrollableWind class ScrollableWind:public Window,public HScrollbar,public VScrollbar{ //... public: ScrollableWind(int top, int left, int bottom, int right); ~ScrollableWind( ); }; ScrollableWind::ScrollableWind (int t, int l, int b, int r): Window(t, l, b, r), HScrollbar(t, r+1, b-1, r),VScrollbar(b-1,l-1,b,r+1) { //... } 1. 多继承派生类的定义: class Derived: 基类 1, 基类 2,…{ }; 2. 派生类对象多次初始化同一基类数据成员问题。 public private protected
派生类对象多次初始化同一基类数据成员问题 假设【例8.1】中,类Window、HScrollbar、VScrollbari都是从同一个基类Port 派生,即: class Port{/*...*; class Window:public Port{/*..* class HScrollbar:public Port{/*...*; class VScrollbar:public Port{/*...* class ScrollableWind:public Window,public HScrollbar,public VScrollbar {/*...*/) Port Port Port 祖先类 创建ScrollableWnd对象时, Port的构造函数通过3条不同 Window HScrollbar VScrollbar 父类 的路径,被调用了3次,从而 将显示端口初始化3次。即, 1个子类有3个同名祖先类, ScrollableWnd 子类 不符合实际! Scrollable Wnd派生树图 7
7 派生类对象多次初始化同一基类数据成员问题 假设【例8.1】中,类Window 、HScrollbar 、VScrollbar都是从同一个基类Port 派生,即: class Port{ /*…*/}; class Window:public Port{ /*…*/}; class HScrollbar:public Port{ /*…*/}; class VScrollbar:public Port{ /*…*/}; class ScrollableWind:public Window,public HScrollbar,public VScrollbar{/*…*/}; 创建ScrollableWnd对象时, Port的构造函数通过 3条不同 的路径,被调用了 3次,从而 将显示端口初始化 3次。即, 1个子类有 3个同名祖先类, 不符合实际! Port Port Port Window HScrollbar VScrollbar ScrollableWnd ScrollableWnd派生树图 祖先类 父类 子类
8.2虚基类 虚基类 Port 祖先类 如何实现:1个子类通 过3条不同的路径到达 Window HScrollbar VScrollbar 父类 同一个祖先类?即,创 建Scrollable Wnd对象 ScrollableWnd 子类 时,显示端口Port仅被 初始化1次? ScrollableWnd派生树和存储空间 class Window:virtual public Port{/*...*; class HScrollbar:public virtual Port{/*...*; virtual和派生方式可以互换位置 class VScrollbar:public virtual Port{/*...*); class Scrollable Wind:public Window,public HScrollbar,public VScrollbar (/*..* 上二页 下二页 8
8 8.2 虚基类 如何实现:1个子类通 过3条不同的路径到达 同一个祖先类?即,创 建ScrollableWnd对象 时,显示端口Port仅被 初始化1次? Port Window HScrollbar VScrollbar ScrollableWnd ? 祖先类 父类 子类 ScrollableWnd派生树和存储空间 class Window:virtual public Port{ /*…*/}; class HScrollbar:public virtual Port{ /*…*/}; class VScrollbar:public virtual Port{ /*…*/}; class ScrollableWind:public Window,public HScrollbar,public VScrollbar{/*…*/}; 虚基类 virtual和派生方式可以互换位置 上一页 下一页
虚基类使用说明 1.仅用于多继承,因为同一个类不能多次作为某个派生类的直 接基类,但可多次作为其间接基类,从而引起存储空间的浪 费和其他问题。 2.同一颗派生树中的同名虚基类,共享同一个存储空间;其构 造函数和析构函数仅执行1次;且构造函数尽可能最早执行, 而析构函数尽可能最晚执行。回上页 3.如果虚基类与基类同名,则它们将分别拥有各自的存储空 间,只有同名虚基类才共享存储空间,而同名基类则拥有各 自的存储空间。 4.虚基类和基类同名必然会导致二义性访问,编译程序会对这 种二义性访问提出警告。当出现这种情况时,要么将基类说 明为对象成员,要么将基类都说明为虚基类。分析【例83】 9
9 虚基类使用说明 1. 仅用于多继承,因为同一个类不能多次作为某个派生类的直 接基类,但可多次作为其间接基类,从而引起存储空间的浪 费和其他问题。 2. 同一颗派生树中的同名虚基类,共享同一个存储空间;其 构 造函数和析构函数仅执行 1 次,且构造函数尽可能最早执行, 而析构函数尽可能最晚执行。回上页 3. 如果虚基类与基类同名,则它们将分别拥有各自的存储空 间,只有同名虚基类才共享存储空间,而同名基类则拥有各 自的存储空间。 4. 虚基类和基类同名必然会导致二义性访问,编译程序会对这 种二义性访问提出警告。当出现这种情况时,要么将基类说 明为对象成员,要么将基类都说明为虚基类。分析【例8.3 】
【例8.3】说明虚基类的二义性访问问题。 #include void main(void) struct A { int a; Ee(0)月 A(int x){a=x;} /cout长<"a="<e.a;/出现二义性? cout<<"a="<<e.B::a; struct B:Af cout<<"a="<<e.D::a; 输出: B(int x):A(x){ a=5 a=0 struct C{ C(){} 为 为解决e.a产生的二义性,要么将E struct D:virtual A,Cf 的基类B说明为对象成员,要么将 D(int x):A(x) B的基类A说明为虚基类。若将B的 }; 基类A说明为虚基类,则e.a、 struct E:B,D{ E(int x):A(x),B(+5),D(x+10){} e.B:a及e.D:a都表示虚基类A的 成员a。 10
10 【 例8.3 】说明虚基类的二义性访问问题。 说明虚基类的二义性访问问题。 #include struct A{ int a; A(int x) { a=x; } }; struct B: A{ B(int x):A(x) { } }; struct C{ C( ) { } }; struct D: virtual A, C{ D(int x):A(x) { } }; struct E: B, D{ E(int x):A(x), B(x+5), D(x+10) { } }; void main(void) { E e(0); //cout<<"a="<<e.a; cout<<"a="<<e.B::a; cout<<"a="<<e.D::a; } 输出: a=5 a=0 为解决e.a产生的二义性,要么将 E 的基类 B说明为对象成员,要么将 B的基类 A说明为虚基类。若将 B 的 基类 A说明为虚基类,则e.a 、 e.B::a 及e.D::a都表示虚基类 A 的 成员a 。 //出现二义性 ?
83派生类成员一 同名问题 当派生类有多个基类或虚基类时,基类或虚基类的 成员之间可能出现同名;派生类和基类或虚基类的 成员之间也可能出现同名。 ■ 当多个数据成员或函数成员的名称相同时,必须通 过面向对象的作用域解析,或者用作用域运算符:指 定要访问的成员,否则就会引起二义性问题。 。基类成员间的同名问题【例82】 。派生类成员与基类成员同名问题【例84】 ·虚基类与基类的成员同名问题【例85】 11
11 当派生类有多个基类或虚基类时,基类或虚基类的 成员之间可能出现同名;派生类和基类或虚基类的 成员之间也可能出现同名。 当多个数据成员或函数成员的名称相同时,必须通 过面向对象的作用域解析,或者用作用域运算符::指 定要访问的成员,否则就会引起二义性问题。 基类成员间的同名问题 【例8.2】 派生类成员与基类成员同名问题【例8.4】 虚基类与基类的成员同名问题【例8.5】 8.3 派生类成员——同名问题