Download presentation
Presentation is loading. Please wait.
1
第3章 继承和派生
2
主要内容 派生类 访问控制 虚函数 基类与派生类之间的转换
3
引 述 [引例]: 一个类中包含了若干数据成员和成员函数。在不同的类中,数据成员和成员函数是不相同的。但有时两个类的内容基本相同或一部分相同。如以下两个类定义:
4
class Personnel { public: void display() { cout<<“num:”<<num<<endl; cout<<“name:”<<name<<endl; cout<<“sex:”<<sex<<endl; } private: int num; string name; char sex; };
5
class Student { public: void display() { cout<<“num:”<<num<<endl; cout<<“name:”<<name<<endl; cout<<“sex:”<<sex<<endl; cout<<“age:”<<age<<endl; cout<<“address:”<< addr <<endl; } private: int num; string name; char sex; int age; char addr[20]; }; [问题]: 可以看到类Student 有相当一部分是类Personnel原来已有的。那么能否利用原来声明的类Personnel作为基础,再加上新的内容去构造类Student呢?
6
C++提供的继承机制能解决上述问题。 在C++中,所谓“继承”就是在已存在的类的基础上建立一个新的类。已存在的类称为“基类”或“父类”;新建立的类称为“派生类”或“子类”。 基类 派生类 人 欧洲人 亚洲人 中国人 韩国人 日本人
7
一个新类从已有的类(基类)那里获得其已有特性,这种现象称为类的继承。 从已有的类(基类)产生一个新的子类(派生类),称为类的派生。
继承分类: 一个派生类只从一个基类派生,这称为单继承。 一个派生类有两个或多个基类的称为多重继承。 例如: A B C D AB ABC 单继承 多重继承 多重继承
8
继承的好处: 当原来使用的数据结构不适应新系统的需求,或原来提供的功能需要扩充,或原来的性能不能满足现在的要求时,可以构建派生类。派生类的成员函数可以调用基类的成员函数,并在此基础上增加必要的程序代码; 当需要完全改变原有操作的实现算法时,可以构建派生类。在派生类中可以实现一个与基类成员函数同名而算法不同的成员函数;当需要增加新功能时,可以在派生类中定义一个新的成员函数。 继承性还使得用户在开发新的应用系统时不必完全从零开始,可以继承原有的相似系统的功能或者从类库中选取适用的类,再派生出新的类以实现所需要的功能。
9
3.1 派生类 1. 派生类的定义 一般语法形式: class 派生类名:继承方式 基类名1,
“派生类名”是继承原有类的特性而生成的新类的名称。 1. 派生类的定义 一般语法形式: class 派生类名:继承方式 基类名1, 继承方式 基类名2, ……, 继承方式 基类名n { //派生类成员声明 }; “继承方式”指出 派生类从基类继承来的成员的访问权限。 “基类名”是已有类的名称。
10
[说明]: 每一个“继承方式”只限定对紧随其后的基类。
继承方式关键字为private、protected和public,分别表示私有继承、保护继承和公有继承。如果不显式指明,则系统默认为私有继承(private)。 [例如]: class Student: public Personnel { public: void display_1() { cout<<“age:”<<age<<endl; cout<<“address:”<< addr <<endl; } private: int age; char addr[20]; };
11
2. 派生类生成过程 构成一个派生类包括三个方面的工作: [例如]:设有如下的Point(点)类。 吸收基类成员 改造基类成员 添加新的成员
class Point { public: Point(int i=0,int j=0) {x=i; y=j;} void Move(int xOff, int yOff) { x+=xOff; y+=yOff; } int GetX() { return x;} int GetY() { return y;} private: int x,y; };
12
从Point类派生出新的Rectangle(矩形)类的声明如下:
class Rectangle :public Point // 派生类声明部分 { public: // 新增公有成员函数 Rectangle(int x, int y, int w, int h):Point(x,y) { width=w; height=h;} int GetW() { return width;} int GetH() {return height;} int Area() { return width*height;} private: // 新增数据成员 int width, height; }; 吸收基类成员 添加新的成员 X改造基类成员
13
1) 吸收基类成员——重用的过程 在C++的类继承中,派生类将继承它的所有基类中的所有成员(构造函数、析构函数和COPY构造函数、赋值函数除外)。 [例如]: 前面的派生类Rectangle继承了基类Point中除构造函数、析构函数和COPY构造函数、赋值函数之外的所有成员:x、y、Move(int,int)、GetX()和GetY()。经过派生过程,这些成员便存在于派生类之中。
14
2) 改造基类成员——原有代码的扩充过程 在派生类中继承了基类的某个成员函数,但这个成员函数的功能可能并不完全符合派生类的需要,就需要在派生类中对继承过来的基类成员函数进行重新定义(改造)。 对基类成员的改造包括两个方面: 对基类成员的访问控制权的修改。主要依靠派生类声明时的继承方式(访问权修饰符)来控制; 对基类数据成员或成员函数的覆盖。即在派生类中声明一个和基类数据或函数同名的成员。
15
[例]:见例exam9.1。 在这里,由于学生和教师都有姓名、性别和编号,因此,将姓名、性别和编号的输入和显示设计成一个Person类。
将Person类作为基类,Student类和Teacher类通过公有方式继承了Person类。
16
3) 添加新的成员-原有代码的扩充过程 可以根据实际情况的需要,给派生类添加适当的数据和函数成员,来实现必要的新增功能。
此外,在声明派生类时,一般还应当自己定义派生类的构造函数、析构函数和COPY构造函数、赋值函数,因为它们是不能从基类继承的。 因此,派生类中添加新的成员后,派生类中的成员包括从基类继承过来的成员(不包括基类中的构造函数、析构函数和COPY构造函数、赋值函数)和自己增加的成员两大部分。
17
[例如]: 从Point类派生出新的Rectangle(矩形)类的声明如下:
class Student: public Personnel { public: void display () { cout<<“age:”<<age<<endl; cout<<“address:”<< addr <<endl; } private: int age; char addr[20]; }; 从Point类派生出新的Rectangle(矩形)类的声明如下: class Rectangle :public Point // 派生类声明部分 { public: // 新增公有成员函数 Rectangle(int x, int y, int w, int h):Point(x,y) { width=w; height=h;} int GetW() { return width;} int GetH() {return height;} int Area() { return width*height;} private: // 新增数据成员 int width, height; };
18
3. 派生类的构造函数和析构函数 派生类不仅继承了基类的成员,还添加了新的成员进行功能的扩充。因此在建立派生类对象时,不仅要对派生类的新增成员进行初始化、还要初始化派生类的基类成员。但是由于基类的构造函数和析构函数不能被继承,因此派生类构造函数必须负责调用基类构造函数,并对其所需要的参数进行设置。 同样,对派生类对象的清理工作也需要加入新的析构函数。
19
1) 派生类的构造函数_I 一般格式: 派生类名(派生类构造函数总参数表): 基类名1(参数表1), ……, 基类名n(参数表n), {
成员对象名1(参数表n+1), ……, 成员对象名m(参数表n+m) { // 派生类构造函数体 };
20
1) 派生类的构造函数_II 调用顺序:(“先父母—>再客人—>后自己”)
执行基类的构造函数—>执行成员对象的构造函数(如果有成员对象)—>执行派生类的构造函数。 [说明]: (1)当派生类有多个基类时,各个基类的构造函数的调用顺序取决于定义派生类时声明的顺序(定义自左向右),而与在派生类构造函数的成员初始化列表中给出的顺序无关。 (2)如果派生类的基类也是一个派生类,则每个派生类只需负责它的直接基类的构造调用,依次上溯。
21
[例]:class Base {int p1,p2; public: Base(int i1,int i2) { p1=i1; p2=i2;
} Base(int x) { p2=x; } }; class Derived:Base { int p3; Base obj1, obj2; // 成员对象 Derived(int x1,int x2,int x3,int x4,int x5):Base(x1,x2),obj1(x3,x4),obj2(x2) { p3=x5; main() { Derived d(27,28,100,200,-50);//调用构造
22
“先父母—>再客人—>自己”
[分析]: “先父母—>再客人—>自己” 在定义Derived类的对象d时,构造函数的调用过程: Base(x1,x2) obj1(x3, x4) obj2(x2) Derived(int x1,int x2,int x3,int x4,int x5)
23
[例]:class Base 各个数据初始化为: p1=27; p2=28; obj.p1=100; obj.p2=200; p3=-50;
{int p1,p2; public: Base(int i1,int i2) { p1=i1; p2=i2; } Base(int x) { p2=x; } }; class Derived:Base { int p3; Base obj1, obj2; // 成员对象 Derived(int x1,int x2,int x3,int x4,int x5):Base(x1,x2),obj1(x3,x4),obj2(x2) { p3=x5; main() { Derived d(27,28,100,200,-50);// “先父母—>再客人—>自己” 各个数据初始化为: p1=27; p2=28; obj.p1=100; obj.p2=200; p3=-50; obj2.p2=28。 [1],[2] [3] [4]
24
[说明]: 在派生类构造函数的初始化列表中,使用父类类名来调用父类构造函数,使用成员对象名来调用内层类的构造函数。
当使用父类或内层类的有参数的构造函数来完成基类成员或对象成员的初始化时,即使派生类构造函数本身无需完成任何工作(函数体为空),也必须定义派生类的构造函数。 如果在定义派生类构造函数时省略父类初始串列,则意味着使用父类的缺省构造函数来初始化基类成员。在这种情况下如果父类中只定义了有参数的构造函数,而没有定义无参数或全部参数都有缺省值的构造函数,则在编译时会产生编译错误 『因为类定义了有参数的构造函数时,就不会产生缺省构造函数』。
25
2) 派生类的析构函数 “父母<—客人<—自己”
派生类的析构函数在执行过程中也要对基类和成员对象进行操作,但它的执行顺序与执行构造函数时的顺序正好相反,即: 执行派生类的析构函数,对派生类新增的普通成员进行清理。 调用成员对象析构函数,对派生类中的成员对象进行清理。 调用基类的析构函数,对从基类继承来的成员进行清理。 “父母<—客人<—自己”
26
见例exam9.2派生类的构造函数和析构函数的调用。
[说明]: 派生类析构函数的定义与基类无关,与没有继承关系的类中的析构函数的定义完全相同。 派生类析构函数只负责对新增普通成员的清理工作,系统会自己调用基类及成员对象的析构函数进行相应的清理工作。 [例]: 见例exam9.2派生类的构造函数和析构函数的调用。
27
3.2 访问控制(继承方式) 在派生类中,对基类的继承方式可以有public(公有的),private(私有的)和protected(保护的)3种。 继承方式的不同决定了基类成员在派生类中的访问属性不同。
28
1. 公有继承_I 在定义一个派生类时将基类的继承方式指定为public的,称为公有继承,用公有继承方式建立的派生类称为公有派生类,其基类称为公有基类。 当类的继承方式为公有继承时,基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有(派生类中不可访问)。
29
1. 公有继承_II 公有基类在派生类中的访问属性如下表: [例]: 基类的成员 私有成员 公有成员 保护成员 在派生类中的访问属性
例exam9.3 点point类的公有继承,求矩形的位置及矩形面积。 基类的成员 私有成员 公有成员 保护成员 在派生类中的访问属性 不可访问 公有 保护
30
2. 私有继承_I 在声明一个派生类时将基类的继承方式指定为private的,称为私有继承,用私有继承方式建立的派生类称为私有派生类,其基类称为私有基类。 当类的继承方式为私有继承时,基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类私有。
31
2. 私有继承_II 私有基类在派生类中的访问属性如下表: [例]:
修改:例exam9.3点point类 为私有继承,求矩形的位置及矩形面积。 观察:问题? 基类的成员 私有成员 公有成员 保护成员 在派生类中的访问属性 不可访问 私有 私有派生类若再派生子类,子类中将无法访问父类成员。所以私有派生用得很少
32
3. 保护继承_I 1) 保护继承: 在声明一个派生类时将基类的继承方式指定为protected的,称为保护继承。用保护继承方式建立的派生类称为保护派生类,其基类称为保护基类。 当类的继承方式为保护继承时,基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。
33
3. 保护继承_III 保护基类在派生类中的访问属性如下表: [例]: 见例exam9.5 保护成员和保护继承的作用 。 ①- ⑨ 正确?
基类的成员 私有成员 公有成员 保护成员 在派生类中的访问属性 不可访问 保护
34
#include <iostream>
using namespace std; class A { public: int x; protected: int a1; private: int a2; }; class B1:protected A void fun(); void B1::fun() a1=1; // ① 正确,派生类中可以直接访问基类的保护成员 a2=2; // ② 错误,派生类中不能直接访问基类的私有成员 x=3; } class B2:public A int main() A obj1; B1 obj2; B2 obj3; obj1.x=10; // ③ 正确,类A的对象obj1可以直接访问类A的公有成员 obj1.a1=20; // ④ 错误,类A的对象obj1不能直接访问类A的保护成员 obj1.a2=30; // ⑤ 错误,类A的对象obj1不能直接访问类A的私有成员 obj2.x=11; // ⑥ 错误,经过保护继承后,基类的公有成员成为派生类 // 中的保护成员。派生类的对象不能直接访问保护成员 obj2.a1=22; // ⑦ 错误,派生类的对象不能直接访问从基类继承来的保护成员 obj3.x=15; // ⑧ 正确,经过公有继承后,派生类的对象可以直接访问从 // 基类继承来的公有成员 obj3.a1=25; // ⑨ 错误,即使是公有继承,派生类的对象也不能直接访问从基类 // 继承来的保护成员 return 0;
35
3. 保护继承_I I 2) 保护成员与私有成员的异同: 相同点:保护成员与私有成员不能被类外对象访问;
不同点:保护成员可以被派生类的成员函数访问。 因此,基类声明的私有成员,在任何派生类都是不能被访问的,若希望在派生类中能访问它们,应当把它们声明为保护成员。
36
[小结]: 基类中的成员 在公有派生类中的访问属性 在私有派生类中的访问属性 在保护派生类中的访问属性 私有成员 不可访问 公有成员 公有
保护成员 基类成员在派生类中的访问属性
37
派生类中的成员的访问属性 派生类中的成员 在派生类中 在派生类外部 在下层公有派生类中 派生类中访问属性为公有的成员 可以
派生类中访问属性为保护的成员 不可以 派生类中访问属性为私有的成员 派生类中访问中不可访问的成员 派生类中的成员的访问属性
38
4. 基类成员的访问属性 1) 同名成员 在定义派生类时,C++语言允许在派生类中说明的成员与基类中的成员名字相同。如果在派生类中定义了与基类成员同名的成员,则称为派生类成员覆盖了基类同名成员。 在派生类中使用这个名字意味着访问在派生类中重新定义的成员。 若要在派生类中使用基类的同名成员,格式如下: 基类名::成员名
39
[例]: class Base {protected: int a, b; public: Base(int i, int j)
{a=i; b=j; } }; class Derived: public Base { int b, c; //同名成员b Derived(int x1,int x2,int x3,int x4):Base(x1,x2) { b=x3;c=x4;} void setc(int x) {c=x+b+Base∷b;} 在派生类中使用这个名字意味着访问在派生类中重新定义的成员。 在派生类中使用基类的同名成员的格式如下: 基类名::成员名
40
派生类成员支配原则 A(a) 如果同一个成员名在两个具有继承关系的类中进行了定义,那么,在派生类中所定义的成员名具有支配地位。
class A { public: int a() { return 1; } }; class B : virtual public A { public: float a() {return float(1.2345); } }; class C : virtual public A { }; class D : public B, public C { }; void main() { D d; cout<<d.a()<<endl; } 类B的成员名a相比类A的成员名a (即是类C中的成员名a)处于支配地位,这样,编译器将调用类B的成员函数a,而不产生二义性的错误。 A(a) B(a) C() D(B::a,A::a)
41
3.3 虚基类 1. 多重继承中的二义性问题 1) 调用不同基类的相同成员时可能出现二义性
[例如]:A -> B ->C (代码见后) A类 int a; void display(); B类 C类 int b; void show(); 基类A的成员 基类B的成员 派生类C的新增的成员
42
[Error?] class A { public: int a; void display(); }; class B class C: public A, public B { public: int b; void show(); }; int main() { C obj; obj.a=3; obj.display(); return 0; } [问题]:由于基类A和基类B都有数据成员a和成员函数display,编译系统无法判别要访问的是哪一个基类成员。因此,程序编译出错。怎么解决呢? //错误
43
1)使用作用域运算符,用类名对成员加以限定。
[解决方法]: 1)使用作用域运算符,用类名对成员加以限定。 [例如]: obj.A::a=3; obj.A::display(); 2)在派生类中定义同名成员。 [例如]:将上述的C类声明为 class C: public A, public B { public: int a; void display(); }; obj.a=3; // 相当于obj.C::a=3; 此时,派生类的新成员就覆盖了基类的同名成员,直接使用成员名只能访问到派生类的成员。
44
成员的访问权限不能区分有二义性的同名成员。 [例如]:
[说明]: 成员的访问权限不能区分有二义性的同名成员。 [例如]: class A { public: void fun(); }; class B { protected; class C : public A, public B { }; 虽然类C中的两个fun()函数,一个公有,一个被保护的,但: C obj; obj.fun();//错误,仍是二义的。 C obj; obj.fun(); // obj.A::fun()? obj.B::fun()? X √ X
45
2) 访问共同基类的成员时可能出现二义性 [例如]: N类 A类 B类 C类
46
[问题]: 怎样访问类A中从基类N继承下来的成员? class C: public A, public B { public:
class N { public: int a; void display(); }; class A: public N int a1; class B: public N int a2; class C: public A, public B { public: int a3; }; int main() { C obj; obj.a=1; return 0; } [问题]: 怎样访问类A中从基类N继承下来的成员? //错误,有二义性
47
前述两种二义性归根结底的原因:在继承共同基类时有多份相同成员
[解决方法]: 应当通过类N的直接派生类名来指出要访问的是类N的哪一个派生类中的基类成员。 [例如]: obj.A::a=1; obj.A::display(); [注意]: 用obj.N::a这种形式是错误的!因为它并不能指出有效的访问路径,到底是从类A继承而来的,还是从类B继承而来的。 [例]:见例exam9.6。 前述两种二义性归根结底的原因:在继承共同基类时有多份相同成员
48
2. 虚基类的概念 C++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员,这样就避免了在派生类中访问这些成员时产生二义性。
假设类A、类B、类C和类D之间的继承关系如下图所示: A类 B类 C类 D类
49
普通继承 A类 B类 C类 D类 int data; void fun(); int data_b; int data_c;
从基类继承的成员 派生类D的新增的成员 B类 int data_b; C类 int data_c; D类 int B::data; int C::data; void B::fun(); void C::fun(); int data_d; void fun_d();
50
虚基类继承 将类A声明为虚基类,方法如下: class A //声明基类A {…};
class B: virtual public A //声明类B是类A的公有派生类,A是B的虚基类 class C: virtual public A //声明类c是类A的公有派生类,A是C的虚基类
51
在派生类B和C中作了上面的虚基类声明后,派生类D中的成员如下图所示。
虚基类继承 在派生类B和C中作了上面的虚基类声明后,派生类D中的成员如下图所示。 基类成员只保留一次 D类 int data; int data_b; int data_c; void fun(); int data_d; void fun_d(); A类 B类 C类 virtual virtual virtual virtual
52
因此,将一个基类声明为虚基类,必须在定义各派生类时在基类的名称前面加上关键字virtual,格式如下:
class 派生类名: virtual 继承方式 基类名 { // 声明派生类成员 } 经过这样声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,即基类成员在内存中只留有一个备份。
53
为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。例如,
[注意]: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。例如, A类 B类 C类 D类 E类 virtual virtual E中A基类成员保留几次? A基类成员保留2次!
54
普通继承 A类 B类 C类 D类 E类 int data; void fun(); int data_b; void fun_d();
从基类继承的成员 派生类D的新增的成员 B类 int data_b; C类 int data_c; D类 int data_d; E类 int B::data; int C::data; int D::data; int B::data_b; int C::data_c; int C::data_d; void B::fun(); void C::fun(); void D::fun(); void fun_d();
55
cout<<e.B::data; cout<<e.C::data; cout<<e.D::data;
E e; e.B::data =1; e.C::data =2; e.D::data =3; cout<<e.B::data; cout<<e.C::data; cout<<e.D::data; cout<<e. data; B::data、C::data、D::data独立存储单元 1 2 3 二义性
56
virtual基类继承 A类 E类 C类 D类 int data; void fun(); int data_c; int data_d;
从基类继承的成员 派生类D的新增的成员 B类 int A::data; void A::fun(); int data_b; C类 int data_c; D类 int data_d; E类 B::data、C::data共享存储单元; int D::data; int B::data_b; int C::data_c; int C::data_d; B::fun()、 C::fun()共享存储单元; void D::fun(); void display(){}; virtual virtual
57
cout<<e.B::data; cout<<e.C::data; cout<<e.D::data;
E e; e.B::data =1; e.C::data =2; e.D::data =3; cout<<e.B::data; cout<<e.C::data; cout<<e.D::data; cout<<e. data; B::data、C::data共享存储单元 2 3 二义性
58
virtual基类继承 A类 E类 C类 D类 int data; void fun(); int data_c; int data_d;
从基类继承的成员 派生类D的新增的成员 B类 int A::data; void A::fun(); int data_b; C类 int data_c; D类 int data_d; E类 B::data、C::data、 D::data共享存储单元; int B::data_b; int C::data_c; int C::data_d; B::fun()、 C::fun() 、 D::fun();共享存储单元; void display(){}; virtual virtual virtual
59
cout<<e.B::data; cout<<e.C::data; cout<<e.D::data;
E e; e.B::data =1; e.C::data =2; e.D::data =3; cout<<e.B::data; cout<<e.C::data; cout<<e.D::data; cout<<e. data; B::data、C::data、D::data共享存储单元; 3 ,3,没有二义性
60
3. 虚基类的构造函数 对于虚基类的任何派生类,其构造函数不仅负责调用直接基类的构造函数,还需负责调用虚基类的构造函数。 [例如]:
class Base { public: Base(int i) { basedata=i; } private: int basedata; };
61
class B1: virtual public Base
B1(int i, int j) : Base(i), b1(j){ } private: int b1; }; class B2: virtual public Base B2(int i, int j) : Base(i), b2(j){ } int b2; class Derived: public B1, public B2 Derived(int i, int m, int n, int k) : B1(i,m), B2(i,n), Base(i), d(k) { } int d;
62
#include <iostream>
using namespace std; class Base { public: Base(int i):b(i) { cout<<"Base constructor. b="<<i<<endl; } ~Base() { cout<<"Base destructor. b="<<b<<endl; } int GetB() { return b; } private: int b; }; class B1: virtual public Base { public: B1(int i, int j) : Base(i), b1(j){cout<<"B1 constructor. b1="<<b1<<endl; } int b1; class B2: virtual public Base B2(int i, int j) : Base(i), b2(j){cout<<"B2 constructor. b2="<<b2<<endl; } int b2; class Derived: public B1, public B2 Derived(int i, int m, int n, int k) : B1(i,m), B2(i,n), Base(i), d(k) {cout<<"Derived constructor. d="<<d<<endl; } int d; int main() Derived obj(1,2,3,4); return 0; }
63
[比较]: 对于普通基类,派生类的构造函数只负责调用其直接基类的构造函数以初始化其直接基类的数据成员。 对于虚基类,必须在派生类中对虚基类初始化。
64
[问题]: 在上例中类Derived的构造函数通过初始化表调用了虚基类的构造函数Base,而类B1和类B2的构造函数也通过初始化表调用了虚基类的构造函数Base,这样虚基类的构造函数岂不是被调用了3次? [解答]: C++编译系统只执行最派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B1和类B2)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。 由于继承结构的层次可能很深,规定在建立对象时所指定的类称为最派生类。 C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的C++虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。
65
构造函数:“先父母—>再客人—>后自己”
当在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数的执行。 构造函数:“先父母—>再客人—>后自己” 【先虚基类构造函数,后非虚基类构造函数】 [例]: 例exam9-7 例exam9-7-1 ( 没有明确指出虚基类Base构造 .则调用虚基类缺省的Base::Base() ,一次!)
66
3.4 基类与派生类之间的转换 问题1:派生类对象可以向基类对象转换吗? [解答]:
在设计类的层次结构时,应该使得派生类在逻辑上是一种“特殊的”基类。例如,卡车是一种汽车,猫是哺乳动物。 因此,在任何需要基类对象的地方使用派生类对象,是有意义的、合乎逻辑的。 在满足一定的限定条件的前提下,C++语言可以自动地把一个派生类对象隐式地转换成一个基类对象。
67
在这种情况下,实际上是用派生类对象从基类继承来的数据成员的值,给基类对象相应数据成员赋值。
基类数据成员 基类成员函数 派生类 派生类数据成员 派生类成员函数 赋值时此部分舍弃不用
68
[解答]: 问题2:基类对象可以向派生类对象转换吗?
由于通常基类在逻辑上未必是某个派生类,例如,汽车未必是卡车,哺乳动物也并不等于猫。因此,在需要派生类对象的地方使用基类对象来代替,是不合逻辑的,也是不正确的。 实际上,派生类对象中拥有的数据成员,在基类对象中不一定都有。因此,如果使用基类对象给派生类对象赋值,赋值后派生类对象的某些数据成员的值将是无定义的。 因此,C++语言不允许使用基类对象给派生类对象赋值。
69
1. 赋值兼容规则 能够把一个派生类对象隐式地转换成一个基类对象的前提条件是:派生类从其基类公有派生。 例如,下面定义的两个类:
class Base {…}; class Derived: public Base {…}; 则派生类Derived的对象具备自动转换成其基类Base的对象的条件。
70
可以把派生类对象的地址赋值给指向基类的指针。 可以把指向派生类对象的指针赋值给指向基类对象的指针。
C++允许以下几种形式的转换: 可以用派生类对象给基类对象赋值。 可以用派生类对象来初始化基类的引用。 可以把派生类对象的地址赋值给指向基类的指针。 可以把指向派生类对象的指针赋值给指向基类对象的指针。
71
1) 可以用派生类对象给基类对象赋值。 [例]: Derived d;
Base b; Derived d; b=d; //用派生类Derived的对象d对基类Base的对象b赋值
72
2) 可以用派生类对象来初始化基类的引用。 [例]: Derived d;
Base &br=d; //定义基类Base对象的引用变量br,并用派生类Derived的对象d对其初始化
73
3) 可以把派生类对象的地址赋值给指向基类的指针。 [例]:
Derived d; Base *bptr=&d; 这种形式的转换,是在实际应用程序中最常见到的。
74
Derived*dptr=(Derived*)bptr;
4) 可以把指向派生类对象的指针赋值给指向基类对象的指针。 [例]: Derived *dptr; Base *bptr=dptr; [说明]: 如果希望把一个基类指针转换成一个派生类指针,必须显式地使用强制类型转换机制。 Derived*dptr=(Derived*)bptr;
75
[例]:见例exam10.2。 #include <iostream> using namespace std;
class Base { public: void Who() { cout<<"I am base class!"<<endl; } }; class Derived1:public Base void Who() { cout<<"I am Derived1 class!"<<endl; } class Derived2:public Base void Who() {cout<<"I am Derived2 class!"<<endl; }
76
[运行结果]:? int main() { Base base_obj; Base *p; Derived1 one_obj;
Derived2 two_obj; p=&base_obj; p->Who(); p=&one_obj; //基类指针p指向派生类Derived1的对象one_obj one_obj.Who(); p=&two_obj; //基类指针p指向派生类Derived2的对象two_obj two_obj.Who(); return 0; } [运行结果]:?
77
[运行结果]: I am base class! I am Derived1 class! I am Derived2 class!
int main() { Base base_obj; Base *p; Derived1 one_obj; Derived2 two_obj; p=&base_obj; p->Who(); //I am base class! p=&one_obj; //基类指针p指向派生类Derived1的对象one_obj p->Who(); //I am base class ! 依然执行基类的who() one_obj.Who(); //I am Derived1 class ! p=&two_obj; //基类指针p指向派生类Derived2的对象two_obj p->Who(); //I am base class ! 依然执行基类的who() two_obj.Who(); //I am Derived2 class return 0; } [运行结果]:?
78
[分析]: 从程序运行结果可以看出,指向基类的指针p,不管赋给它的是基类对象base_obj的地址还是派生类对象one_obj和two_obj的地址,语句“p->Who();”调用的始终是基类中定义的版本。 这是由于虽然一个基类指针可以指向其派生类对象,但指针本身的类型并没有改变。因此,系统认为它所指向的仍然是一个基类对象,于是就只能调用其基类的成员函数Who()。
79
课堂练习
80
D D A 1.若类A和类B的定义如下: class A { public: int i,j; void get(); };
class B:A { int i,j; protected: int k; public: void make(); }; void B::make() { k=i*j; } 则其中( )是非法的表达式。 A.void get(); B.int k; C.void make(); D.k=i*j; 2.在派生类中能够直接访问的是基类的( )。 A.公有成员和私有成员 B.保护成员和私有成员 C.不可访问的和私有的成员 D.公有成员和保护成员 3. 假定MyClass为—个类,则执行MyClass a,b(2),*p;语句时,自动调用该类的构造函数( )次? A B C. 4 D. 5 D D A
81
BABDA BDCBB CDCAA 1. 已知程序段如下: class base { public: int b; };
class base1 : public base { }; class base2 : public base { }; class base3 : public base1,base2 { }; 设有主函数语句 int a; base3 d; 下列语句正确的是______: A. a=d.b; B. a=d.base1::b C. a=d.base::b D.a=b 4.下列各类函数中,______不属于类的成员。 A.构造函数 B. 复制的构造函数 C. 赋值函数 D.友元函数 5、派生类的对象对它的基类成员中( )是可以访问的。 A、公有继承的公有成员;B、公有继承的私有成员; C、公有继承的保护成员;D、私有继承的公有成员; 6、设置虚基类的目的是( )。 A、简化程序; B、消除二义性; C、提高运行效率; D、减少目标代码; 13.关于多继承二义性的描述中,错误的是:( ) A.一个派生类的两个基类中都有某个同名成员,在派生类中对这个成员的访问可能出现二义性 B.解决二义性的最常用方法是对成员名的限定法。 C.派生类和基类中都有某个同名成员,也存在二义性问题 D.一个派生类是从两个基类派生出来,而这两个基类又有一个共同基类,对该基类成员的访问可能出现二义性 BABDA BDCBB CDCAA
82
//12 ; 访问权符不能解决二义性,obj.fun()是二义的
2.下面的代码中有一处错误,给出错误语句行号,并说明出错原因。 #include <iostream> //1 using namespace std; //2 class A { public: //3 void fun() { }; //4 }; class B { protected: //5 void fun() { }; //6 class C : public A, public B //7 { }; //8 void main() //9 { //10 C obj; //11 obj.fun(); //12 } //13 //12 ; 访问权符不能解决二义性,obj.fun()是二义的
83
Destructor B Destructor A 3.写出下列程序的运行结果。 #include <iostream>
using namespace std; class A{ public: A( ){ } virtual void func( ) { cout<<"Destructor A"<<endl; } virtual ~A( ) { func(); } }; class B:public A{ B( ){ } void func() { cout<<"Destructor B"<<endl; } ~B( ) { func(); } int main(){ B b; A &a=b; return 0; } Destructor B Destructor A
84
Z:: make_y1() Y::y1为私有成员
2.下面给出的类定义中其中有四个成员函数,分析编译时那些成员函数会出错,为什么? class X { protected: int x; public: X(int i) {x=i;} }; class Y:private X { int y1; protected: int y2; Y(int i,int j1,int j2):X(i) { y1=j1; y2=j2; } void make_y1() { y1=x*y1;} void make_y2( ){ y2=x*y2; } class Z:public Y { int z1; int z2; Z(int i1,int i2,int i3,int i4,int i5):Y(i1,i2,i3) { z1=i4;z2=i5; } void make_y1() { y1=x*y1*z1; } void make_y2() { y2=x*y2*z2; } Z:: make_y1() Y::y1为私有成员 Z:: make_y2() Y从X私有继承
85
课后思考 下面是一个类的测试程序,请为之设计一个能使用如下测试程序的C++类: #include <iostream.h>
void main() { MyCls TTT(10,20); TTT.sum=TTT.add(); // 功能: add()用于计算10和20之和 TTT. show() ; // 功能: 输出10和20之和
86
class D:public B1,public B2 { public:
3. 分析程序, 给出下面程序的输出结果: #include <iostream> using namespace std; class A { public: void SetA(int i) { a=i; } int GetA() { return a; } private: int a; }; class B1: virtual public A int GetB1() { return b; } void SetNum(int i,int j) { SetA(i); b=j; } void Print() { cout<<"a="<<GetA()<<",b="<<b<<endl; } int b; class B2: virtual public A int GetB2() { return b; } { SetA(i); b=j; } class D:public B1,public B2 { public: void SetNum(int i,int j,int m,int n,int k) B1::SetNum(i,j); B2::SetNum(m,n); d=k; } void SetD(int i) { d=i; } int GetD() { return d; } private: int d; }; int main() D obj1,obj2; obj1.SetNum(1,2,3,4,5); obj2.B1::SetNum(10,20); obj2.B2::SetNum(30,40); obj2.SetD(50); cout<<"Derived from B1 :"; obj1.B1::Print(); cout<<"Derived from B2 :"; obj1.B2::Print(); return 0;
87
小 结 定义方法、生成过程和构造函数。 公有继承、私有继承和保护继承。 转换条件、4种形式。 派生类: 访问控制:
虚基类(virtual): 多重继承的二义性问题及解决方法、虚基类的概念和构造函数。 基类与派生类之间的转换: 转换条件、4种形式。
Similar presentations