300 likes | 434 Views
第 七 章 继承机制. 继承机制的作用 继承成员的访问控制规则 继承成员的调整 类型兼容性 类层次中的构造函数与析构函数 多重继承 重复继承. A. 基类. B. 派生类. § 7.1 继承 机制 一、继承. 继承是类与类之间的一种关系 定义: “ 类 B 继承类 A ” , 或者说 “ 类 A 派生类 B ” 图解为: 则在类 B 中除了自己定义的成员之外,还自动包括了类 A 中定义的数据成员与成员函数,这些自动继承下来的成员称为类 B 的 继承成员 。. 二、继承的语法 1. 继承的语法:. class 派生类名 : 基类类名表 {
E N D
第七章继承机制 继承机制的作用 继承成员的访问控制规则 继承成员的调整 类型兼容性 类层次中的构造函数与析构函数 多重继承 重复继承
A 基类 B 派生类 §7.1 继承机制一、继承 • 继承是类与类之间的一种关系 • 定义:“类B继承类A”,或者说“类A派生类B”图解为: • 则在类B中除了自己定义的成员之外,还自动包括了类A中定义的数据成员与成员函数,这些自动继承下来的成员称为类B的继承成员。
二、继承的语法1.继承的语法: • class 派生类名: 基类类名表{ public: 公有成员说明列表; protected: 受保护成员说明列表; private: 私有成员说明列表; }; 其中基类类名表的格式为: access 基类类名1, ……, access 基类类名n access为继承访问控制符,规定了派生类对基类的继承方式,可为public,private或者protected, • 继承访问控制符可省略,此时认为为private
例: BASE BASE D class BASE { …… }; class A:public BASE{ ……// 单继承 }; class B:private BASE, public D{ …… // 多重继承 }; class C:public A, B{ …… }; A B C 私有派生
成员访问控制 类自身 派生类 其他类 public 可访问 可访问 可访问 protected 可访问 可访问 不可访问 private 可访问 不可访问 不可访问 2.保护访问控制属性:protected • 在protected后定义的是保护段,其中的数据成员或成员函数称为受保护成员:具有公有成员与私有成员的双重角色。 • 一个类的受保护成员,对于其子孙类(派生类)的成员函数来说是公有的,对类本身及后代类之外定义的其他函数则是私有成员。 • 例: class BASE { private: int x; protected: int i, j; }; class D:public BASE{ void make(); }; void D::make() { int k = i* j ; …… }
3.继承成员函数的重定义 • 派生类可以重新定义基类的成员函数,覆盖基类的同名函数 • 例: class DATE { public: DATE(int yy = 0, int mm = 0, int dd = 0); // 构造函数 void set_date(int yy, int mm, int dd); // 设置日期 void get_date(int& yy, int& mm, int& dd); // 取日期 void print_date();// 以ANSI格式(yy.mm.dd)打印日期 protected: int year, month, day; // 年、月、日 }; class EUROPE_DATE: public DATE { public: void print_date();// 以欧洲格式(dd-mm-yy)打印日期 void print(int isANSI); };
4.通过类名限定符在派生类中使用基类的同名成员4.通过类名限定符在派生类中使用基类的同名成员 void DATE::print_date() { // 以ANSI格式(yy.mm.dd)打印日期 cout<<year<<“.”<<month<< “.”<<day<<“\n”; } void EUROPE_DATE::print_date() { // 以欧洲格式(dd-mm-yy)打印日期 cout<< day <<“-”<<month<< “-”<<year<<“\n”; } void EUROPE_DATE::print(int isANSI) { if (isANSI) DATE::print_date(); else print_date(); } void main() { EUROPE_DATE test ; test.print_date(); //调用EUROPE_DATE中重定义的同名函数 //以欧洲格式(dd-mm-yy)打印 test.DATE::print_date(); //以ANSI格式(yy.mm.dd)打印 }
二、继承访问控制规则 • 公有继承(公有派生)、私有继承、保护继承 • 无论采 用什么派 生的方式, 派生类中 都不能访 问基类的 私有成员
例: class BASE{ protected: int i, j; public: void get_ij(); private: int x_temp; }; class Y1:public BASE{ 公有派生:在Y1类中,i、j是受保护成员 float yMember; get_ij()是公有成员 }; class Y2:protected BASE{保护派生:在Y2类中,i、j是受保护成员 ……get_ij()变成受保护成员 }; class Y3:private BASE{ 私有派生:在Y3类中,i、j、 get_ij()都变 ……成私有成员 };
obj1 i J x_temp 函数指针 i J x_temp yMember 函数指针 obj2 三、派生类对象的存储组织 • 派生类的对象不仅存放了在派生类中定义的非静态数据成员,而且也存放了从基类中继承下来的非静态数据成员。 • 例: BASE obj1; Y1 obj2;
四、类型兼容性1.赋值运算的类型兼容性 • 类型的赋值兼容性规则允许将后代类的对象赋值给祖先类,但反之不成立。 • 例:BASE obj1; Y1 obj2; obj1 = obj2 ; 把obj2中基类部分的内容赋给obj1 obj2 = obj1 ; • 但此规则只适用于公有派生,只有公有派生类才能兼容基类类型
2.参数传递与对象初始化的类型兼容性 • 指向基类对象的指针也可指向公有派生类对象 BASE *p ; Y1 *p1; p = &obj1; p1 = &obj1; p = &obj2; p1 = &obj2; p = p1 ; • 与赋值运算类型兼容性相同
§7.2 继承与构造函数、析构函数一、构造函数与析构函数的调用次序 1. 构造函数的调用次序 在创建一个派生类的对象时 • 先调用其基类的构造函数 • 再调用本类对象成员的构造函数 • 最后才调用本类的构造函数 2. 析构函数的调用次序 • 先调用本类的析构函数 • 再调用本类对象成员的析构函数 • 最后才调用其基类的析构函数
例: #include <iostream.h> class C { public: C() // 构造函数 { cout << "Constructing C object.\n"; } ~C() // 析构函数 { cout << "Destructing C object.\n"; } }; class BASE { public: BASE() // 构造函数 { cout << "Constructing base object.\n"; } ~BASE() // 析构函数 { cout << "Destructing base object.\n"; } }; class DERIVED: public BASE { C mOBJ; public: DERIVED() // 构造函数 { cout << "Constructing derived object.\n";} ~DERIVED() // 析构函数 { cout << "Destructing derived object.\n"; } };
int main() { DERIVED obj; // 声明一个派生类的对象 // 什么也不做,仅完成对象obj的构造与析构 return 0; } 运行结果: Constructing base object. Constructing C object. Constructing derived object. Destructingderived object. DestructingC object. Destructingbase object.
二、向基类构造函数传递实际参数 • 给基类构造函数传递实际参数是通过向派生类构造函数传递实际参数以及初始化列表来间接实现传递的。 • 带初始化列表的派生类构造函数的一般形式 派生类名 ( 参数表 ) : 基类名 ( 调用基类构造函数参数表 ) { 派生类构造函数体 }
例: #include <iostream.h> class Base { int private1, private2; public: Base(int p1, int p2) { private1 = p1; private2 = p2;} int inc1() { return ++private1; } int inc2() { return ++private2; } void display() { cout<<"private1 = "<<private1 <<", privte2 = "<<private2<<"\n";} }; class Derived:private Base{ int private3; Base private4; public: Derived(int p1, int p2, int p3, int p4, int p5):Base(p1,p2),private4(p3,p4) { private3 = p5 ; } int inc1() { return Base::inc1();} int inc3() { return ++private3 ; }
obj 17 18 -15 1 2 private1 从基类继承 private2 private3 private4.private1 private4.private2 void display() { Base::display(); private4.display(); cout<<"private3 = "<<private3<<"\n"; } }; void main() { Derived obj( 17, 18, 1, 2, -5); obj.inc1(); obj.display(); } 输出结果: private1 = 18, private2 = 18 private1 = 1, private2 =2 private3 = -5 18
§7.3 多重继承一、多重继承 • 多重继承:一个类由多个基类派生而来 单继承: 一个类由单个基类派生而来 • 多重继承的语法: class 派生类名: access 基类名1, ……, access 基类名n{ … }; 基类名1 基类名2 …… 基类名n 派生类名
二、多重继承的名字冲突问题 • 名字冲突:指两个基类具有相同名字的成员时,在派生类中这个名字会产生二义性,即编译程序无法确定派生类的对象使用该名字时应调用哪一基类中的版本。 例: class BASE1 { public: void show() { cout << i << "\n"; } protected: int i; }; class BASE2 { public: void show() { cout << j << "\n"; } protected: int j; };
// 多重继承引起名字冲突:DERIVED的两个基类BASE1和BASE2有相同的名字show()。 class DERIVED: public BASE1, public BASE2 { public: void set(int x, int y) { i = x; j = y; } }; // 派生类在编译时不出错:C++并不禁止名字冲突的产生 int main() { DERIVED obj; // 声明一个派生类的对象 obj.set(5, 7); // set()是DERIVED类自身定义的 // obj.show();// 二义性错误,编译程序无法决定调用哪一个版本 obj.BASE1::show(); // 显式地调用从BASE1继承下来show() obj.BASE2::show(); // 显式地调用从BASE2继承下来show() return 0; }
名字冲突的解决方法 • 使用时,用作用域运算符明确指明使用那个基类的成员函数 obj.BASE1::show(); • 在派生类中重定义有名字冲突的成员 class DERIVED: public BASE1, public BASE2 { public: void set(int x, int y) { i = x; j = y; } void show() { cout << i << "\n"; cout << j << "\n"; } }; int main() { DERIVED obj; // 声明一个派生类的对象 obj.set(5, 7); // set()是DERIVED类自身定义的 obj.show();//无二义性问题,调用的是DERIVED中新定义的版本 obj.BASE1::show(); // 仍然可调用从BASE1继承下来show() obj.BASE2::show(); // 仍然可调用从BASE2继承下来show() return 0; }
三、多重继承的构造函数和析构函数 • 多个基类构造函数的调用次序是按基类在被继承时所声明的次序、从左到右依次调用的,与它们在派生类构造函数实现中的初始化列表中出现的次序无关。 • 例: class DERIVED: public BASE2, public BASE1 { public: DERIVED(int x, int y): BASE1(x), BASE2(y) { cout << "Constructing derived object.\n"; } …… }; …… DERIVED obj(3,4); 则obj在创建时,先调用BASE2的构造函数,然后调用BASE1的构造函数,最后才执行自己DERIVED的构造函数
B B A C D §7.4 重复继承 一、重复继承 1. 定义 • 定义:指一个派生类多次继承同一个基类 • C++中关于继承的限制 • 不允许直接或间接让一个类继承自己 • 不允许一个派生类直接继承同一个基类两次以上 • 不允许一个基类即是直接基类又是间接基类
B B A C D B A C D 2. 重复继承的两种类型 • 复制继承:被多次重复继承 的基类有多个实体副本 • 共享继承:被多次重复继承的基类只有一个实体副本 例:
BASE BASE BASE1 BASE2 DERIVED obj BASE1. BASE.i BASE1. j BASE2. BASE.i BASE2. k sum 函数指针 二、重复继承的二义性问题 • 若在继承时没有作特殊声明,此时采用的是复制继承,会导致重复继承的二义性问题。 • 例: class BASE {public: int i;}; class BASE1: public BASE { public: int j; }; class BASE2: public BASE { public: int k; }; class DERIVED: public BASE1, public BASE2 { public: int sum; }; void main() { DERIVED obj; // 声明一个派生类对象 // obj.i = 3; //错误,编译程序无法确定使用i的哪一份副本 obj.j = 5; // 正确的,使用从BASE1继承下来的j obj.k = 7; // 正确的,使用从BASE2继承下来的k }
解决二义性的方法 • 如不改变重复继承的方法(还是复制继承),则采用作用域运算符::明确指明采用哪个副本 int main() { DERIVED obj; obj.BASE1::i = 3; …… } • 改用共享方式继承:用虚基类机制保证任何派生类中只提供一个基类的副本
三、虚基类 • 定义:虚基类是当基类被继承时,在基类的继承访问控制关键字前面加上关键字virtual来定义的。 • 普通基类与虚基类之间的唯一区别只有在派生类重复继承了某一基类时才表现出来,虚基类用于实现共享继承
obj BASE1. BASE.i BASE1. j BASE2. k sum 函数指针 BASE BASE1 BASE2 DERIVED class BASE {public: int i;}; class BASE1: virtual public BASE { public: int j; }; class BASE2:virtual public BASE { public: int k; }; class DERIVED: public BASE1, public BASE2 { public: int sum; }; int main() { DERIVED obj; // 声明一个派生类对象 obj.i = 3; //正确 obj.j = 5; // 正确的,使用从BASE1继承下来的j obj.k = 7; // 正确的,使用从BASE2继承下来的k return 0; }
虚基类的构造函数与析构函数 • 对虚基类构造函数的调用总是先于普通基类的构造函数。 • 虚基类的唯一副本只被初始化一次 • C++中构造函数的调用次序 (1) 最先调用虚基类的构造函数。 (2) 其次调用普通基类的构造函数,多个基类则按派生类声明时列出的次序、从左到右调用,而不是初始化列表中的次序。 (3) 再次调用对象成员的构造函数,按类声明中对象成员出现的次序调用,而不是初始化列表中的次序。 (4) 最后执行派生类的构造函数。