第四章 继承与派生 Chapter 4 Inheritance and Derivation 14:41:26
继承 (Inheritance) 当已经实现了一个类后,我们可以从这个类 派生出新的类,这种派生机制就叫做继承。 比如已经有了学生类,那么 研究生也是学生类的一种, 因此学生所有的属性他都具备 但又有自己独特的属性 比如:研究方向,导师 继承使得代码的可重用性大大增加 Student GradStudent 14:41:26
练习 定义1个CPerson类,其属性(保护类型)有: 姓名、性别和年龄 从CPerson类派生出CStudent类,增加属性: 学号、入学时间和入学成绩; 从CPerson类派生出CTeacher类,增加属性: 职务、部门和工作时间; CStudent类派生出CGraduate类,增加属性: 研究方向和导师; CGraduate和CTeacher共同派生出在职研究生 类CGradOnWork 14:41:26
继承体系 CPerson CTeacher CStudent CGraduate CGradOnWork 本章主要围绕这个例子展开!!! 14:41:26
继承方式 继承语法形式 class B {……}; class D : [private | protected | public] B { 可分为 私有继承:private 保护继承:protected 公有继承:public 14:41:26
派生类和基类的关系 派生类是在基类的继承上添加成员,因此: 派生类继承了基类的所有成员,尽管有些 继承的成员可能是不可访问的 this指针 基类成员 新增加成员 this指针 派生类成员 14:41:26
关于this指针 class Point{ private: double x,y; public: Point(double x, double y){ this->x = x; this->y = y; } static void f(){this->x=0;}//错 }; int main(){ Point p(1,2); this指针是隐含在成员函数的参数列表里,由C++编译器为你自动加上,存放的是对象所占的内存首地址。 由于static成员是类层次上的成员,所以不能在静态成员函数中使用this指针 14:41:26
基类与派生类的关系 派生类对基类成员的访问形式 通过派生类对象直接访问基类成员 在派生类成员函数中直接访问基类成员 通过基类名字限定访问被重载的基类成员 名 下面举例说明 14:41:26
基类与派生类的关系 class B{ //B为基类 int x; public: void set(int i){ x=i;} void setx(int i){ x=i;} void printx(){ cout<<"x="<<x<<endl; } }; 14:41:26
class D:public B{ //D为B的派生类 int y; public: void sety(int p){y=p;} void printy(){ cout<<"y="<<y<<endl; } void setxy(int i,int j){ setx(i); //在派生类成员函数中直接访问基类成员 y=j; } void set(int i,int j){ B::set(i); //访问基类set成员 };
//通过对象访问…. int main(){ D obj; obj. setx(2); //访问基类成员 obj //通过对象访问…... int main(){ D obj; obj.setx(2); //访问基类成员 obj.printx(); //访问基类成员, 输出 x=2 obj.sety(3); //访问派生类成员 obj.set(1,2); //访问派生类set成员 obj.B::set(3); //访问基类set成员 }
基类与派生类对象的关系 14:41:26
基类与派生类对象的关系 基类对象与派生类对象之间可以这样赋值: 把派生类对象赋值给基类对象。 base = derive; //对象 把派生类对象的地址赋值给基类指针: Base *pBase=&derive; //指针 用派生类对象初始化基类对象的引用。 Base base = &derive; //指针 反之则不行,即基类类型的对象或者指针不 能赋值给派生类对象或指针 14:41:26
把派生类对象赋值给基类对象的例子。 class A { int a; public: void setA(int x){ a=x; } int getA(){ return a;} }; class B:public A{ int b; void setB(int x){ b=x; } int getB(){ return b;} int main() { A a,*pa; B b,*pb; a = b; pa = &b; b = a;//错误 pb = &a;//错误 }
基类与派生类对象的关系 说明: ① 不论以哪种方式把派生类对象赋值给基类 对象,都只能访问到派生类对象中的基类子 对象部份的成员,不能访问派生类的自定义 成员。 ②只能把派生类对象赋值给基类对象,不能 把基类对象赋值给派生类对象。 切割现象 14:41:26
公有继承 public 最常见的派生方式 维持基类成员的可访问性 派生类不可直接访问基类的private 成员,可通过基类的公有成员函数 访问 14:41:26
公有接口 私有数据 x Base Derived x y class Base{ int x; //私有 public: void Setx(int x){this->x=x;} void Showx() {cout<<x<<endl;} }; 公有接口 私有数据 Setx() Showx() x Base class Derived:public Base{ int y; public: void Set(int x, int y) { Setx(x); this->y=y; } void Show() { Showx(); cout<<y<<endl; }; Derived Setx() Showx() x Set() Show() y 14:41:26
公有继承 int main() { Derived d; //派生类对象 d.setx(1); //基类里的成员函数 d.showx(); //同上 d.set(2,3); //派生类自己的函数 d.show(); //同上 } 14:41:26
私有继承 基类的中的public成员在派生类中变成了 private 基类的private成员在派生类中仍然不可访问。 class Base{ int x; public: void setx(int x){this->x=x;} void showx(){cout<<x<<endl;} }; 14:41:26
公有接口 私有 x Base Derived x y class Derived:private Base{ int y; //私有 public: void Set(int x, int y) { Setx(x); this->y=y; } void Show() { Showx(); cout<<y<<endl; }; 公有接口 私有 Setx() Showx() x Base Derived Setx() Getx() Showx() x int main(){ Derived d; d.setx(1);//错误 d.showx();//错误 d.set(2,3);d.show(); } Set() Show() y
保护成员 基类中protected的成员 类内部:可以访问 类的使用者:不能访问 类的派生类成员:可以访问类似于 public protected成员的权限介于private和public之 间,主要体现在继承过程中 类似于私有成员 14:41:26
class B { private: int i; protected: int j; public: int k; }; class D: public B {public: void f() { i=1;//cannot access j=2; k=3; } int main() { B b; b.i =1;//cannot access b.j=2; //cannot access b.k=3; } 接口 私有数据 k j i B类 D类 k j i f()
class Base{ int x; protected: int getx(){ return x; } public: void setx(int n){ x=n; } void showx(){ cout<<x<<endl; } }; class Derived:protected Base{ int y; void sety(int n){ y=n; } void sety(){ y=getx();} //访问基类的保护成员 void showy(){ cout<<y<<endl; } 保护继承 基类的public成员在派生类中会变成protected成员,基类的protected和private成员在派生类中保持原来的访问权限。 int main(){ Derived obj; obj.setx(10); //错误 obj.sety(20); obj.showx(); //错误 obj.showy(); }
总结:基类成员在派生类中的访问权限 基类的私有成员只有在本类成员函数中访问,在派生类中不能访问!!! 基类 派生类 public继承 protected继承 private继承 public protected private √ 基类的私有成员只有在本类成员函数中访问,在派生类中不能访问!!!
继承机制下的构造函数和析构函数 之间已经介绍过:类中包含对象成员时的构 造顺序为: 先构造成员 再构造自身 14:41:26
class A { public: A() { cout<<"Constructing A"<<endl;} ~A(){ cout<<"Destructing A"<<endl;} }; class B { B() { cout<<"Constructing B"<<endl;} ~B(){ cout<<"Destructing B"<<endl;} 14:41:26
如果没有继承,对象成员先构造,再构造自身对象 Constructing B Constructing A Constructing C Destructing C Destructing A Destructing B class C { public: C() { cout<<"Constructing C"<<endl;} ~C(){ cout<<"Destructing C"<<endl;} B b; A a; }; int main() { C c; } 如果没有继承,对象成员先构造,再构造自身对象 如果存在继承: class B:public A{} class C:public B{} 结果又当如何?
派生类构造函数的定义 之前介绍过对象成员的构造需要初始化列表 基类对象事实上是派生类对象的一部分,创建派 生类对象必须要调用基类的构造函数,并向它们 传递参数,以完成基类子对象的建立和初始化。 派生类只能采用构造函数初始化列表的方式向基 类或成员对象的构造函数传递参数,形式如下: 派生类构造函数名(参数表): 基类构造函数名 (参数表),成员对象名1(参数表),…{ //…… } 14:41:26
派生类构造函数的定义 class Base{ private: int x; public: Base(int a){ x=a; cout<<"Base constructor x="<<x<<endl; } ~Base(){ cout<<"Base destructor..."<<endl; } }; 14:41:26
class Derived: public Base{ private: int y; public: Derived(int a,int b): Base(a){ //派生类构造函数的 初始化列表 y=b; cout<<"Derived constructor y="<<y<<endl; } ~Derived(){ cout<<"Derived destructor..."<<endl; } }; int main(){ Derived d(1,2);
构造函数和析构函数调用次序 派生类对象的构造 先构造基类 再构造成员 最后构造自身 基类构造顺序由派生层次决定:最远的基类 最先构造 成员构造顺序和定义顺序相同 析构函数的析构顺序与构造顺序相反 14:41:26
class A { public: A() { cout<<"Constructing A"<<endl;} ~A(){ cout<<"Destructing A"<<endl;} }; class B { B() { cout<<"Constructing B"<<endl;} ~B(){ cout<<"Destructing B"<<endl;} class C { C() { cout<<"Constructing C"<<endl;} ~C(){ cout<<"Destructing C"<<endl;}
class D:public C { public: D() { cout<<"Constructing D"<<endl;} ~D(){ cout<<"Destructing D"<<endl;} B b; A a; C c; }; int main() D d; } Constructing C Constructing B Constructing A Constructing D Destructing D Destructing C Destructing A Destructing B
构造函数和析构函数的构造规则 派生类的构造函数只负责直接基类的初始化 C++语言标准有一条规则:如果派生类的基类 同时也是另外一个类的派生类,则每个派生 类只负责它的直接基类的构造函数调用。 这条规则表明当派生类的直接基类只有带参 数的构造函数,但没有默认构造函数时(包 括缺省参数和无参构造函数),它必须在构 造函数的初始化列表中调用其直接基类的构 造函数,并向基类的构造函数传递参数,以 实现派生类对象中的基类子对象的初始化。 14:41:26
当同时存在直接基类和间接基类时,每个派生类只负 责其直接基类的构造。 class A { //基类 int x; public: A(int aa) { x=aa; cout<<"Constructing A"<<endl; } ~A(){ cout<<"Destructing A"<<endl; } };
class B:public A {//A是B的直接基类 public: B(int x):A(x){ cout<<"Constructing B"<<endl; } }; class C :public B{//B是C直接基类,而A是间接基类 C(int y):B(y){ cout<<"Constructing C"<<endl; } void main(){ C c(1); }
多重继承 C++允许一个类从一个或多个基类派生。如果一个 类只有一个基类,就称为单一继承。如果一个类 具有两个或两个以上的基类,就称为多重继承。 多继承的形式如下: class 派生类名:[继承方式] 基类名1,[继承方式] 基类 名2, … { …… }; 其中,继承方式可以是public、protected、 private 14:41:26
多继承的概念和应用 14:41:26
多继承的概念和应用 class Base1{ private: int x; protected: int getx(){ return x; } public: void setx(int a=1){ x=a; } }; 14:41:26
class Base2{ private: int y; public: void sety(int a){ y=a; } int gety(){ return y; } }; class Base3{ int z; void setz(int a){ z=a; } int getz(){ return z; }
class Derived:public Base1,public Base2,public Base3{ private: int d; public: void setd(int a){ d=a; } void display(); }; void Derived::display(){ cout<<"Base1....x="<<getx()<<endl; cout<<"Base2....y="<<gety()<<endl; cout<<"Base3....z="<<getz()<<endl; cout<<"Derived..d="<<d<<endl; } void main(){ Derived obj; obj.setx(1); obj.sety(2); obj.setz(3); obj.setd(4); obj.display();
多继承下的二义性 在多继承方式下,派生类继承了多个基类的 成员,当两个不同基类拥有同名成员时,容 易产生名字冲突问题。 类A和类B是MI的基类,它们都有一个成员函 数f,在类MI中就有通过继承而来的两个同名 成员函数f。 14:41:26
class A { public: void f(){ cout<<"From A"<<endl;} }; class B { void f() { cout<<"From B"<<endl;} class MI: public A, public B { void g(){ cout<<"From MI"<<endl; } void main(){ MI mi; mi.f(); //错误 mi.A::f(); //正确 }
多继承的构造函数与析构函数 派生类必须负责为每个基类的构造函数提供 初始化参数,构造的方法和原则与单继承相 同。 构造函数的调用次序仍然是先基类,再对象 成员,然后才是派生类的构造函数。 基类构造函数的调用次序与它们在被继承时 的声明次序相同,与它们在派生类构造函数 的初始化列表中的次序没有关系。 多继承方式下的析构函数调用次序仍然与构 造函数的调用次序相反。 14:41:26
虚拟继承 引入的原因:重复基类 DeptHead Faculty Person name … Administrator name … 派生类间接继承同一基类使得间接基类(Person)在派生类中有多份拷贝,引发二义性。 14:41:26
虚拟基类在派生类中只存在一份拷贝,解决了基类数据成员的二义性问题 14:41:26
虚拟继承的实现 虚拟继承virtual inheritance的定义 语法 虚基类virtual base class class derived_class : virtual […] base_class 虚基类virtual base class 被虚拟继承的基类 在其所有的派生类中,仅出现一次 14:41:26
类A是类B、C的基类,类D从类B、C继承,在类D中调用基类A的成员会产生二义性。 class A { public: void vf() { cout<<"I come from class A"<<endl; } }; class B: public A{}; class C: public A{}; class D: public B, public C{}; int main() { D d; d.vf(); // 错误 d.B::vf(); // 正确 d.C::vf(); //正确 } A Vf() A Vf() B Vf() C Vf() D B.A::Vf() C.A::Vf()
改为虚拟继承不会产生二义性。 class A { public: void vf() { cout<<"I come from class A"<<endl; } }; class B: virtual public A{}; class C: virtual public A{}; class D: public B, public C{}; void main() { D d; d.vf(); // 正确 } 菱形继承 A Vf() B Vf() C Vf() D A::Vf()
虚拟继承的构造次序 虚基类初始化时构造函数的调用顺序不同; 若基类由虚基类派生而来,则派生类必须提供对 间接基类的构造(即在构造函数初始列表中构造 虚基类,无论此虚基类是直接还是间接基类) 调用顺序的规定: 先调用虚基类的构造函数,再调用非虚基类 的构造函数 若同一层次中包含多个虚基类,这些虚基类的 构造函数按它们说明的次序调用 若虚基类由非基类派生而来,则仍然先调用基 类构造函数,再调用派生类构造函数 14:41:26
虚拟继承的实现 虚基类的执行次序分析。 class A { int a; public: A(){ cout<<"Constructing A"<<endl; } }; class B { B(){ cout<<"Constructing B"<<endl;} 14:41:26
//B是普通基类,A是虚基类 class B1:public B ,virtual public A{ public: B1(int i){ cout<<"Constructing B1"<<endl; } }; //A是普通基类,B是虚基类 class B2:public A,virtual public B { B2(int j){ cout<<"Constructing B2"<<endl; } class D: public B1, public B2 { D(int m,int n): B1(m),B2(n){ cout<<"Constructing D"<<endl; A a; int main(){ D d(1,2); 3 1 4 5 2 6 7 8
虚拟继承的实现 虚基类由最终派生类初始化 在没有虚拟继承的情况下,每个派生类的 构造函数只负责其直接基类的初始化。但 在虚拟继承方式下,虚基类则由最终派生 类的构造函数负责初始化。 在虚拟继承方式下,若最终派生类的构造 函数没有明确调用虚基类的构造函数,编 译器就会尝试调用虚基类不需要参数的构 造函数(包括缺省、无参和缺省参数的构 造函数),如果没找到就会产生编译错误。 14:41:26
类A是类B、C的虚基类,类ABC从B、C派生,是 继承结构中的最终派生类,它负责虚基类A的 初始化。 class A { int a; public: A(int x) { a=x; cout<<"Virtual Bass A..."<<endl; } };
class B: virtual public A { public: B(int i):A(i){ cout<<"Virtual Bass B..."<<endl; } }; class C: virtual public A{ int x; C(int i):A(i){ cout<<"Constructing C..."<<endl; x=i; } class ABC:public C, public B { ABC(int i,int j,int k):C(i),B(j),A(i) //L1,这里必须对A进行初始化 { cout<<"Constructing ABC..."<<endl; } void main(){ ABC obj(1,2,3);