710 likes | 985 Views
第 5 章 派生类与继承. 主要内容. 5.1 继承与派生类的概念 5.2 派生类成员的访问属性 5.3 派生类构造函数和析构函数 5.4 多继承 5.5 虚基类 5.6 子对象与父对象复制兼容 5.7 继承与组合. 5.1 继承与派生类. 5.1.1 为什么要使用继承 引入继承的目的在于为代码重用提供有效手段。. 5.1.2 派生类的声明. class employee{ protected: char name[10]; int age;
E N D
主要内容 5.1 继承与派生类的概念 5.2 派生类成员的访问属性 5.3 派生类构造函数和析构函数 5.4 多继承 5.5 虚基类 5.6 子对象与父对象复制兼容 5.7 继承与组合
5.1 继承与派生类 5.1.1 为什么要使用继承 引入继承的目的在于为代码重用提供有效手段。
5.1.2 派生类的声明 class employee{ protected: char name[10]; int age; char sex; char department[20]; float salary; public: void print(); }; class person { protected: char name[10]; int age; char sex; public: void print(); };
//定义一个基类 class person{ protected: char name[10]; int age; char sex; public: //…… }; //定义一个派生类 class employee:public person { protected: char department[20]; float salary; public: //…… };
声明一个派生类的一般格式为: class 派生类名:继承方式 基类名 { //派生类新增的数据成员和成员函数 };
由类person继承出类employee可以采用下面的三种格式之一:由类person继承出类employee可以采用下面的三种格式之一: (1) 公有继承 class employee:public person{ //… }; (2) 私有继承 class employee:private person{ //… }; (3) 保护继承 class employee:protected person{ //… };
5.1.3 派生类生成过程 从已有类派生出新类时,可以在派生类内完成以下几种功能: (1) 可以增加新的数据成员; (2) 可以增加新的成员函数; (3) 可以重新定义基类中已有的数据成员和成员函数; (4) 可以改变现有成员的属性。
5.2 派生类成员的访问属性 派生类对基类成员的访问形式主要有以下两种: (1) 内部访问: 由派生类中新增成员对基类继承来的成员的访问。 (2) 对象访问: 在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
5.2.1 公有继承的访问规则 基类的public成员公有继承派生类public成员; 基类的protected成员公有继承派生类protected成员; 基类的private成员公有继承派生类中不可访问。 例5.1一个公有继承的例子
void showB() { cout<<”x=”<<x<<endl; cout<<”y=”<<y<<endl; cout<<”z=”<<z<<endl; } private: int z;}; int main() { B obj; obj.setB(10,20,30); obj.showA(); obj.showB(); return 0; } #include<iostream.h> class A{ //声明一个基类 public: void setA(int i,int j) { x=i; y=j; } void showA() { cout<<”x=”<<x<<endl; cout<<”y=”<<y<<endl; } private: int x; protected: int y;}; class B:public A //声明一个公有派生类 { public: void setB(int i,int j,int k) { setA(i,j); //setA()在派生类中是public成员,可以访问 z=k; }
基类成员 private成员 public成员 protected成员 内部访问 对象访问 不可访问 不可访问 可访问 可访问 可访问 不可访问 表5-1 公有继承的访问规则
5.2.2 私有继承的访问规则 基类的public成员私有继承派生类private成员; 基类的protected成员私有继承派生类private成员; 基类的private成员私有继承派生类中不可访问。 例5.2一个私有继承的例子
void showB() { cout<<x; cout<<y<<endl; } private: int y; }; main() { B obj; obj.setA(10); obj.showA(); obj.setB(10,20); obj.showB(); return 0; } #include<iostream.h> class A{ //声明一个基类 public: void setA(int i) { x=i; } void showA() { cout<<x<<endl; } private: int x; }; class B:private A{ public: void setB(int i,int j) { setA(i); y=j; }
基类成员 private成员 public成员 protected成员 内部访问 对象访问 不可访问 不可访问 可访问 不可访问 可访问 不可访问 表5-2 私有继承的访问规则
5.2.3. 保护继承的访问规则 基类的public成员保护继承派生类protected成员; 基类的protected成员保护继承派生类protected成员; 基类的private成员保护继承派生类中不可访问。 例5.4 保护继承的访问规则。
class B:protected A{ public: int a; void setB(int i,int j,int k,int l,int m,int n) { x=i; //非法,在派生类B中x为不可访问成员应该是setA(i) y=j; //合法,在派生类B中y是protected成员 z=k; //合法,在派生类B中z是protected成员 a=l; //合法,访问自己的公有成员 b=m;//合法,访问自己的私有成员 c=n; //合法,访问自己的保护成员 } #include<iostream.h> class A{ //声明一个基类 public: int z; void setA(int i){x=i; } int getA(){ return x;} private: int x; protected: int y; };
void showB() {cout<<”x=”<<x<<endl;//非法,在派生类中x不可访问 cout<<”x=”<<getA()<<endl;//合法,getA()在B中是protected成员 cout<<”y=”<<y<<endl; //合法,y在B中是protected成员 cout<<”z=”<<z<<endl; //合法,z在B中是protected成员 cout<<”a=”<<a<<endl; cout<<”b=”<<b<<endl; cout<<”c=”<<c<<endl;} private: int b; protected: int c;}; main() { B obj; obj.setB(10,20,30,40,50,60); obj.showB(); cout<<”a=”<<obj.a<<endl; //合法,c在B中是public成员}
基类成员 private成员 public成员 protected成员 内部访问 对象访问 不可访问 不可访问 可访问 不可访问 可访问 不可访问 表5-3 保护继承的访问规则
在基类中的访问属性 继承方式 在派生类中的访问属性 private public 不可直接访问 private private 不可直接访问 private protected 不可直接访问 public public public public private private public protected protected protected public protected protected private private protected protected protected 5.2.4 基类成员在派生类中的访问属性
5.3 派生类的构造函数和析构函数 5.3.1派生类构造函数 派生类构造函数和析构函数的执行顺序: 通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数; 当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
例5_5 基类和派生类的构造函数及析构函数的执行顺序。 #include<iostream.h> class Base{ public: Base(){ cout<<"Constructing base class\n"; } //基类的构造函数 ~Base(){ cout<<"Destructing baes class\n"; } //基类的析构函数 }; class Derive:public Base{ public: Derive(){cout<<"Constructing derived class\n";} //派生类的构造函数 ~Derive(){cout<<"Destructing derived class\n";} //派生类的析构函数 }; main() { Derive op; return 0; }
派生类构造函数和析构函数的构造规则 在C++中,派生类构造函数的一般格式为: 派生类::派生类名(参数总表):基类名(参数表) { // 派生类新增成员的初始化语句 } 注意:这是基类有构造函数且含有参数时使用
例5_6 当基类含有带参数的构造函数时, 派生类构造函数的构造方法。 #include<iostream.h> class Base { public: Base(int n) //基类的构造函数 { cout<<"Constructing base class\n"; i=n; } ~Base() //基类的析构函数 { cout<<"Destructing base class\n"; } void showi() { cout<<i<<endl; } private: int i; };
class Derive :public Base{ public: Derive(int n,int m):Base(m) // 定义派生类构造函数时, { // 缀上基类的构造函数 cout<<"Constructing derived class"<<endl; j=n; } ~Derive() //派生类的析构函数 { cout<<"Destructing derived class"<<endl; } void showj(){ cout<<j<<endl;} private: int j; }; main() { Derive obj(50,60); obj.showi(); obj.showj(); return 0; }
当派生类中含有内嵌对象成员时,其构造函数的一般形式为:当派生类中含有内嵌对象成员时,其构造函数的一般形式为: 派生类名(参数总表):基类名(参数表1),内嵌对象名1(内嵌对象参数表1),…,内嵌对象名n(内嵌对象参数表n) { // 派生类新增成员的初始化语句 } 例5.6
#include<iostream.h> class A //声明类 { public: A(int i) //类A的构造函数 { a=i; cout<<"A Constructor "<<a<<endl; } void dispA() { cout<<"a="<<a<<endl; } private: int a; };
class B //声明基类 { public: B(int j):obj1(j+10) //基类构造函数,缀上其对象成员构造函数 { b=j; cout<<"B Constructor"<<endl; } void dispB() { cout<<"b="<<b<<endl; } private: int b; A obj1; };
class C:public B //声明派生类,公有继承B类 { public: C(int k):B(k-2),obj(k+2) //派生类的构造函数,缀上基类构造函数和对象成员构造函数 { c=k; cout<<"C Constructor"<<endl; } void dispC() { obj.dispA(); //合法,调用自己对象成员的public成员函数 dispB(); //合法,调用基类的函数,继承后属性是public cout<<"c="<<c<<endl;} private: int c; A obj; //类A的对象,作为派生类的对象成员 }; void main() { C c(2);c.dispC();}
在定义派生类对象时,构造函数的执行顺序如下:在定义派生类对象时,构造函数的执行顺序如下: · 调用基类的构造函数(有多个基类时,调用顺序由它们在类中声明的顺序确定); ·调用内嵌对象成员的构造函数(有多个对象成员时,调用顺序由它们在类中声明的顺序确定); ·派生类的构造函数体中的内容 撤消对象时,析构函数的调用顺序与构造函数的调用顺序正好相反。
5.3.2 派生类析构函数 (1)当派生类中不含对象成员时 ●在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数; ●在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。 (2)当派生类中含有对象成员时 ●在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数; ●在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。例5.7
5.4 多继承 派生类只有一个基类,这种派生方法称为单基派生或单继承。 当一个派生类具有多个基类时,这种派生方法称为多基派生或多继承。
5.4.1 多继承的声明 有两个以上基类的派生类声明的一般形式如下: class 派生类名:继承方式1 基类名1,…,继承方式n 基类名n{ // 派生类新增的数据成员和成员函数 }; 例5.8
void setC(int i,int j,int k) { z=k;setA(i);setB(j);} void showC() {showA();showB(); cout<<"z="<<z<<endl; } }; void main() { C obj; obj.setA(1); //合法,setA()在派生类C中是public成员 obj.showA(); //合法,showA()在派生类C中是publi成员 obj.setC(1,2,3); obj.showC(); //obj.setB(5); //非法,setB()在派生类C中是private成员 //obj.showB(); //非法,showB()在派生类C中是private成员 } #include<iostream.h> class A //声明基类 {private: int x; public: void setA(int i){x=i;} void showA(){cout<<"x="<<x<<endl;} }; class B //声明基类 {private: int y; public: void setB(int j){y=j;} void showB(){cout<<“y=”<<y<<endl;} }; class C:public A,private B //声明派生类C,公有继承A私有继承B {private: int z; public:
5.4.2 多继承的构造函数与析构函数 多继承构造函数定义的一般形式如下: 派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n) { // 派生类新增成员的初始化语句 } 例5.9
class A3 {private: int a3; public: A3(int k){a3=k; cout<<"A3 Constructor:"<<a3<<endl;} ~A3(){ cout<<"A3 Destructor"<<endl;} }; class D:public A1,public A2 {private: int d; A3 obj; public: D(int i,int j,int k,int l):A2(i),A1(j),obj(k) {d=l; cout<<"D Constructor:"<<d<<endl;} ~D(){ cout<<"D Destructor"<<endl;} }; void main() {D dd(1,2,3,4);} #include<iostream.h> class A1 {private: int a1; public: A1(int i) {a1=i; cout<<“A1 Constructor:”<<a1<<endl;} ~A1() {cout<<“A1 Destructor”<<endl;} }; class A2 { private: int a2; public: A2(int j) {a2=j; cout<<"A2 Constructor:"<<a2<<endl; } ~A2() {cout<<“A2 Destructor”<<endl;} };
上堂课回顾 1.派生类构造函数 *派生类构造函数构造规则。 *派生类中含有内嵌对象时构造规则。 2.派生类析构函数 *基类构造函数不被继承(自动执行)。 *与构造函数执行顺序相反。 3.多继承 *概念 *声明 *多继承的构造函数和析构函数
5.4.3 派生类重载基类成员和二义性问题 派生类的成员包括从基类继承来的和自己新增加的成员。可以对基类继承来的成员加以重新定义,这种派生类与基类有同名成员的情况,就是派生类重载基类成员。 说明: (1)经过继承所得成员,如果派生类没有重新定义,那么它会使用基类的成员。 (2)如果派生类重载基类成员,那么就会掩盖基类的同名成员,改用派生类的新版本。如果要使用基类成员,需要采用以下形式: 基类名::成员名 例5.10
void main() { animal a;dog b;cat c; a.call(); b.call(); //调用dog类的call()函数 c.call(); //调用cat类的call()函数 b.animal::call();//调用基类的call()函数 c.animal::call();//调用基类的call()函数 b.eat(); //调用基类的eat()函数 c.eat(); //调用基类的eat()函数 } #include<iostream.h> class animal{ //基类animal public: void call() {cout<<"o…!o…!"<<endl;} void eat() {cout<<"I eat food."<<endl;} }; class dog:public animal //声明派生类dog { public: void call() //重载基类call()函数 {cout<<"won won"<<endl;} }; class cat:public animal //声明派生类cat { public: void call() //重载基类call()函数 {cout<<"meow meow"<<endl;} };
B(show()) A(show()) C 5.4.3 派生类重载基类成员和二义性问题 1.成员函数二义性问题 例5.11
void main() { C obj; obj.set(1,2); //obj.show(); obj.A::show(); obj.B::show(); } #include<iostream.h> class A{ protected: int i; public: void show() {cout<<"i="<<i<<endl;} }; class B{ protected: int j; public: void show() {cout<<"j="<<j<<endl;} }; class C:public A,public B { public: void set(int x,int y) {i=x;j=y;} };
A A C B D 5.4.3 派生类重载基类成员和二义性问题 2.成员变量二义性问题 A—称为派生类D的公共基类 例5.12
void main() { D obj; //obj.b=5; //二义性错误,编译器无法确定是哪一个b obj.C::b=5;//从C继承的b obj.B::b=6;//从B继承的b obj.b1=7; obj.b2=8; obj.d=9; //obj.display(); //二义性错误,无法确定是哪一个display() obj.C::display();//从C类继承的display() obj.B::display();//从B类继承的display() } #include<iostream.h> class A //公共基类A { public: int b; void display() {cout<<"Display A"<<endl;} }; class B:public A //直接基类B { public: int b1; }; class C:public A //直接基类C { public: int b2; }; class D:public B,public C //派生类D { public: int d; };
5.5 虚基类 1、为什么引入虚基类 虽然也可以通过在成员变量名前面加上“类名::”消除其二义性,但解决这个问题的最好方法是使用虚基类。虚基类方法可以保证在任何一个存在公共基类的派生类中,不会存在一个以上的同名成员变量。
例5.13 没有使用虚基类的例子。 #include <iostream.h> class base { public: base(){ a=5; cout<<"base a="<<a<<endl; } protected: int a; }; class base1:public base{ public: base1() { a=a+10; cout<<"base1 a="<<a<<endl; } }; class base2:public base{ public: base2(){a=a+20; cout<<"base2 a="<<a<<endl;} };
class derived:public base1,public base2{ public: derived() { cout<<"base1::a="<<base1::a<<endl; cout<<"base2::a="<<base2::a<<endl; } }; main() { derived obj; return 0; } 程序运行结果如下:: base a=5 base1 a=15 base a=5 base2 a=25 base1::a=15 base2::a=25
base base base1 base2 derived 图5-4 非虚基类的类层次图
2. 虚基类的概念 虚基类:在一个类层次中,如果某个派生类存在一个公共基类,将这个基类设置为虚基类,这时从不同的路径继承过来的该类在内存中保留一个副本。 注意: (1)虚基类并不是在声明基类是声明的,因为它可能做虚基类,可能不做。 (2)为了保证虚基类在派生类中继承一次,应当在该基类的所有直接派生类中声明为虚基类。
2. 虚基类的概念 虚基类的声明是在派生类的声明过程,其语法形式如下: class 派生类名:virtual 继承方式 类名{ //… } 为什么不在基类中使用virtual?
例5_13 虚基类的使用。 #include <iostream.h> class base { public: base( ){ a=5; cout<<"base a="<<a<<endl;} protected: int a; }; class base1: virtual public base{ public: base1( ){ a=a+10; cout<<"base1 a="<<a<<endl;} }; class base2: virtual public base{ public: base2( ){ a=a+20; cout<<"base2 a="<<a<<endl;} };