第七章 继承和派生.doc
第七章 继承和派生一、 基类和派生类1. 在C+中,可以从一个类派生出另一个类,前者称为基类或父类,后者称为派生类或子类。一个派生类可以从一个或多个基类派生。从一个基类派生的继承称单继承,从多个基类派生的继承称多继承。2. 单继承定义格式: class 派生类:继承方式 基类 派生类新定义成员; ; 其中,“派生类”是从“基类”按指定的“继承关系”派生出的、新定义的一个类的名字。“继承方式”有三种:(1)public 公有继承 (2)private 私有继承 (3) protected 保护继承3. 多继承定义格式: class 派生类:继承方式1 基类1,继承方式2 基类2, 派生类新定义成员; ;4. 公有继承:当派生类的继承方式是public继承时,基类的public和protected成员的访问属性在派生类中保持不变,而基类的private成员不可访问,即基类的public和protected成员分别作为派生类的public和protected成员,派生类的其他成员可以直接访问它们。 例1: class B private: int x; protected: int y; public: int z; ; class A:public B private: int a; protected: int b; public: int c; A a; (1) 类B是基类,有3个数据成员x、y、z,分别是private、protected、public属性。类A是派生类,有3个数据成员a、b、c,分别是private、protected、public属性。由于类A是从类B公有派生的,类B中的public、protected属性的成员可继承到类A中来,且保持原来的属性。故类A中有5个数据成员,即a、b、c、y。(2) 可以通过类A的对象a直接访问public成员,即z和c,并不能访问a、b、y数据成员。 (3) 不考虑派生类时,类中的private和protected属性的成员都可以被类的成员函数访问,而都不能被该类的对象直接访问。在派生类中,当公有继承时,基类中protected属性的成员可以作为派生类的protected属性的成员,而private属性的成员不能作为派生类的成员。5. 保护继承:当派生类的继承方式是protected继承时,基类的public和protected成员可以被继承到派生类中,但访问属性都变成protected,而基类的private成员不可访问,即基类的public和protected成员都作为派生类的protected成员,派生类的其他成员可以直接访问它们。例2: class B private: int x; protected: int y; public: int z; ; class A:protected B private: int a; protected: int b; public: int c; A a;类B是基类,有3个数据成员x、y、z,分别是private、protected、public属性。类A是从类B保护派生的,类B中的protected、public属性的成员可继承到类A中,均变成protected属性。故类A中有5个数据成员,即a、b、c、y、z。可以通过类A的对象a直接访问public成员,即c,不能访问a、b、y、z数据成员。6. 私有继承:当派生类的继承方式是private继承时,基类的public和protected成员可以被继承到派生类中,但访问属性都变成private,而基类的private成员不可访问,即基类的public和protected成员都作为派生类的private成员,派生类的其他成员可以直接访问它们。例3: class B private: int x; protected: int y; public: int z; ; class A:private B private: int a; protected: int b; public: int c; A a;类B是基类,有3个数据成员x、y、z,分别是private、protected、public属性。类A是从类B私有派生的,类B中的protected、public属性的成员可继承到类A中,均变成private属性。故类A中有5个数据成员,即a、b、c、y、z。可以通过类A的对象a直接访问public成员,即c,不能访问a、b、y、z数据成员。二、 单继承1. 在单继承中,每个类可以有多个派生类,但是每个派生类只能有一个基类,从而形成树形结构。2. 构造函数不能被继承,故派生类的构造函数必须通过调用基类的构造函数来初始化基类子对象。在定义派生类的构造函数时,除了要对自己的数据成员进行初始化,还要调用基类构造函数使基类的数据成员得以初始化。若派生类中还有子对象,应包含对子对象初始化的构造函数。3. 派生类构造函数一般格式: 派生类名(派生类构造函数总参数表):基类构造函数(参数表1), 子对象名(参数表2) 派生类中数据成员初始化; ;4. 派生类构造函数调用顺序:(1)基类的构造函数;(2)子对象类的构造函数(有的话);(3)派生类构造函数。例4: class A public: A() cout<<”A Constructor”<<endl; ; class B:public A public: B() cout<<”B Constructor”<<endl; ; void main() B b; 执行结果为: A Constructor B Constructor5. 当对象被删除时,派生类的析构函数被执行。析构函数也不能被继承,故在执行派生类的析构函数时,基类的析构函数也将被调用。析构函数的执行顺序是先执行派生类的析构函数,再执行基类的析构函数。例5: class A public: A() cout<<”A Constructor”<<endl; A() cout<<”A Destructor”<<endl; ; class B:public A public: B() cout<<”B Constructor”<<endl; B() cout<<”B Destructor”<<endl; ; void main() B b; 执行结果为: A Constructor B Constructor B Destructor A Destructor6. 派生类构造函数的定义中可以省略对基类构造函数的调用,条件是在基类中必须有默认的构造函数或根本没有定义构造函数。7. 当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。有的情况下,派生类构造函数的函数体可能为空,仅起到参数传递的作用。三、 多继承1. 多继承是指派生类具有多个基类,派生类和每个基类间的关系仍可看作一个单继承。 如: class A ; class B; class C:public A,public B ; 其中派生类C具有两个基类,故为多继承。派生类C的成员包含了基类A和基类B中的成员和它本身的成员。2. 多继承派生类的构造函数格式: 派生类名(总参数表):基类名1(参数表1),基类名2(参数表2), 基类名n(参数表n),子对象名(参数表n+1) 派生类构造函数体; 其中,“总参数表”中各个参数包含了其后的各个分参数表。3. 多继承和单继承派生类的构造函数必须同时负责该派生类所有基类构造函数的调用,且派生类的参数个数必须包含完成所有基类初始化所需的参数个数。4. 多继承派生类构造函数的执行顺序:(1)调用基类的构造函数,调用次序按照它们被继承时说明的次序(从左到右)。(2)调用子对象的构造函数,调用次序按照它们在类中说明的次序。(3)调用派生类的构造函数。5. 对于处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,与派生类构造函数中所定义的成员初始化列表中的各项顺序无关。 例6: class A int a; public: A(int i) a=i; cout<<”A Constructor”<<endl; void disp() cout<<”a=”<<a<<endl; ; class B int b; public: B(int j) b=j; cout<<”B Constructor”<<endl; void disp() cout<<”b=”<<b<<endl; ; class C:public B,public A int c; public: c(int k):A(k-2):B(k+2) c=k; cout<<”C Constructor”<<endl; void disp() A:disp(); B:disp(); cout<<”c=”<<c<<endl; ; void main() C obj(10); obj.disp(); 执行结果为:B Constructor A Constructor C Constructor a=8 b=12 c=106. 多继承中析构函数的执行顺序与构造函数的顺序正好相反。7. 在派生类中对基类成员的访问应该是唯一的,但在多继承下,可能造成对基类中某个成员的访问出现了不唯一的情况,称为对基类成员访问的二义性问题。一般解决方法是采用作用域运算符:。四、 虚基类1. 虚基类说明格式: virtual 继承方式 基类 其中,virtual是虚基类的关键字。虚基类的说明用在定义派生类时,写在派生类名后面。例7: class x protected: int a; public: x() a=10; ; class x1:public x public: x1() cout<<a<<endl; ;class x2:public x public: x2()cout<<a<<endl; ;class y:x1,x2 public: y() cout<<a<<endl; /二义性,是x1:a还是x2:a ;void main() y obj; 此时,通过引入虚基类消除二义性。引入虚基类后,派生类的对象中只存在一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针叫虚基类指针。上面程序应改为: class x1:virtual public x public: x1() cout<<a<<endl; ;class x2:virtual public x public: x2()cout<<a<<endl; ;2. 虚基类的初始化和多继承类的语法一样,但调用派生类构造函数的顺序应遵守以下原则: (1)虚基类的构造函数在非虚基类前调用。(2)若同一层次中包含多个虚基类,则虚基类的构造函数按它们说明的顺序调用。(3)若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类中构造函数的执行顺序。例7: class base1 public: base1() cout<<”class base1”<<endl; ; class base2 public: base2() cout<<”class base2”<<endl; ; class level1:public base2,virtual public base1public: level1() cout<<”class level1”<<endl; ; class level2:public base2,virtual public base1public: level2() cout<<”class level2”<<endl; ; class toplevel:public level1,virtual public level2public: toplevel () cout<<”class toplevel”<<endl; ; void main() toplevel obj; 整个obj对象的各构造函数的执行顺序为: base1()、base2()、level2()、base2()、level1()、toplevel(). 执行结果为: class base1 class base2 class level2 class base2 class level1 class toplevel 3. 一般,当定义虚基类的构造函数时,虚基类只允许定义不带参数的或带默认参数的构造函数。五、 模板和继承1. 类模板可以从类模板中派生,此时两个类模板的类型参数有关联关系。在定义派生类时必须指定这个类模板的类型参数。例8: template <class T> class A public: void dispa(T obj) cout<<obj<<endl; ; template <class T1,class T2> class B:public A<T2> public:void dispb(T1 obj1,T2 obj2) cout<<obj1<<”<<obj2<<endl; ; void main() B<char *,double> obj1; obj1.dispa(12.34); obj1.dispb(“The value is:”56.78); B<int,int> obj2; obj2.dispb(89,10); B<double,char *> obj3; obj3.disp(2.3,”is OK.”); B<char,char *> obj4; obj4.dispb(I,”and you”); 执行结果: 12.34 The value is:56.78 89 10 2.3 is OK. I and you2. 类模板可以从非类模板中派生,此时模板继承非模板的成员,再从类模板实例化模板类,最后从该模板类实例化成对象。 例9: class A int x; public: A(int i) x=i; int getx() return x; void disp() cout<<x<<endl; ; template <class T> class B:public A T y; public: B(T a,int j):A(j) y=a; T gety() return y; void disp() cout<<y<<”<<getx()<<endl; ;void main() A obj(123); obj.disp(); B<int> obj1(1,2); B<double> obj2(1.3,5); B<char *> obj3(“It is”,10); B<char> obj4(=,20);obj1.disp(); obj2.disp(); obj3.disp(); obj4.disp(); 执行结果: 123 1 2 1.3 5 It is 10 =203. 非类模板可以从类模板中派生,此时必须在非模板类中指定基模板类(即将类模板实例化成模板类),再从该非模板类实例化成对象。例10: class A protected: T x; public: A<T>(T i) x=i; ;class B:public A<int> int y; public: B(int i,int j):A<int>(i) y=j; void disp() cout<<”x=”<<x<<”,y=”<<y<<endl; ; void main() B obj(1,2); obj.disp(); 执行结果: x=1,y=2习题 1. C+中的类有两种用法:一种是类的实例化,即生成类的对象,并参与系统的运行;另一种是通过( )派生出新的类。 2. 继承具有( )性,即当基类本身也是某一个类的派生类时,底层的派生类也会自动继承间接基类的成员。 3. 在多继承中,公有派生和私有派生对于基类成员在派生类中的可访问性与单继承的规则( )。 4. 派生可分为( )和( )。由( )得到得派生类,其基类的所有公有成员都只能成为它的私有成员,这些私有成员只能被派生类的成员函数访问,而通过( )无权访问它;( )的意义是基类中所有公有成员在派生类中也都是公有的。 5. 基类的( )不能为派生类的成员访问,基类的( )在派生类中的性质和继承的性质一样,而基类的( )在私有继承时在派生类中成为私有成员,在公有和保护继承时在派生类中仍为保护对象。 6. ( )提供了类对外部的界面,( )只能被类的成员访问,而( )不允许外界访问,但允许派生类的成员访问,这样既有一定的隐藏能力,又提供了开放的界面。 7. 若类A和类B定义如下:( )是非法的表达式。 class A int I,j; public: void get(); / ; class B:A int k; public: void make(); / ; void B:make() k=i*j; 8. 派生类的对象对它的基类成员中( )继承的( )成员是可以访问的。 9. 设置虚基类的目的是( )。 10. 对于派生类的构造函数,在定义对象时构造函数的执行顺序为:先执行( ),再执行( ),后执行( )。 11. 下面说法错误的是( )。 A. 一个派生类可以作为另一个派生类的基类 B. 派生类至少有一个基类 C. 派生类的成员除了它自己的成员外,还包含了它的基类的成员 D. 派生类中继承的基类成员的访问权限到派生类保持不变 12. 下面描述错误的是( )。 A. 派生类是基类的具体化 B. 派生类是基类的子集 C. 派生类是基类定义的延续 D. 派生类是基类的组合 13. 派生类的构造函数的成员初始化列表中,不能包含( )。 A. 基类的构造函数 B. 派生类中子对象的初始化 C. 基类的子对象初始化 D. 派生类中一般数据成员的初始化 14. 关于多继承二义性的描述,错误的是( )。 A. 一个派生类的两个基类中都有某个同名成员,在派生类中对这个成员的访问可能出 现二义性 B. 解决二义性的做常用方法是对成员名的限定 C. 基类和派生类中出现同名函数,也存在二义性问题 D. 一个派生类是从两个基类派生来的,而这两个基类又有一个共同的基类,对该基类成员进行访问时,也可能出现二义性 15. 以下程序的执行结果为( )。 class A public: A(int i,int j) a=i; b=j; void move() int x,int y a+=x; b+=y; void show() cout<<”(”<<a<<”,”<<b<<”)”<<endl; private: int a,b; ; class B:public A public: B(int i,int j,int k,int l):A(i,j),x(k),y(l) void show() cout<<x<<”,”<<y<<endl; void fun()move(3,5); void f1() A:show(); private: int x,y; ; void main() A e(1,2); e.show(); B d(3,4,5,6); d.fun(); d.A:show(); d.B:show(); d.f1(); 16. 阅读程序回答问题。class A public: void f1(); A() i1=10; j1=11; protected: int j1; private: int i1; ; class B:protected A public: void f2(); B() i2=20; j2=21; protected: int j2; private: int i2; ;class C:protected B public: void f3(); c() i3=30;j3=31; protected: int j3; private: int i3; ;(1) 派生类B中成员函数f2()能否访问基类A中的成员f1()、i1和j1?(2) 派生类B的对象b能否访问基类A中的成员f1()、i1、j1?(3) 派生类C中成员函数f3()能否访问直接基类B中得成员f2()和j2?能否访问间接基类A中的成员f1()、j1和i1?(4) 派生类C的对象c能否访问直接基类B中的成员f2()、i2和j2?能否访问间接基类A中得成员f1()、j1和i1?17. 指出下面程序的错误并改正。 class A protected: int n,m; public: void A:set(int a,int b) m=a; n=b;void A:show() cout<<m<<”,”<<n<<endl; ;class B:public A int s; public: void B:sets s=m*n;void B:show() cout<<s<<endl; ; void main() B b; b.set(4,5); b.show(); b.sets(); b.show(); 习题参考答案: 1. 继承 2. 传递 3. 完全相同 4. 公有派生 私有派生 私有派生 派生类的对象 公有派生5. 私有成员 公有成员 保护成员 6. 公有成员 私有成员 保护成员7. k=i*j; 8. 公有 公有 9. 消除二义性10. 基类的构造函数 成员对象的构造函数 派生类本身的构造函数11. D。分析:只有在公有继承方式时,派生类中继承的访问属性为public、protected基类成员到派生类后访问属性保持不变。12. B。分析:在派生类中除了继承基类的成员外,还可以添加新的成员。13. C 14. C 15. (1,2) (6,9) 5,6 (6,9)16. (1)可以访问f1()和j1,不能访问i1。 (2)不可以访问f1(),也不能访问i1和j1。 (3)可以访问f2()和j2,也能访问f1()和j1,不能访问i2和i1。 (4)不可以访问f2()和f1(),其它的也都不能访问。17. 由于派生类B中有一个同名的成员函数show(),在执行b.show()时总是执行B类的成员函数,故出错。修改方法是把B类的成员函数show()函数改成shows()。void B:shows() cout<<s<<endl; ; void main() B b; b.set(4,5); b.show(); b.sets(); b.shows();