导图社区 cpp专题知识框架 ---- 继承与派生类
c 专题只是框架,继承与派生的相关知识点思维导图,希望这份脑图会对你有所帮助。
编辑于2023-06-01 17:40:20 江苏省继承与派生类
继承与派生类
定义
两个过程
保持已有类的特性而构造新类的过程称为继承。
在已有类的基础上新增自己的特性而产生新类的过程称为派生。
两个概念
被继承的已有类称为基类(或父类)。
派生出的新类称为派生类。
目的
继承的目的
代码的重复使用
派生的目的
代码的扩充
整体思路
一般到特殊
分类
单继承
多继承
继承内容
除了构造函数、析构函数、私有成员外的其他成员
继承方式
公有继承
class employee:public person{ //… };
私有继承
class employee:private person{ //… };
保护继承
class employee:protected person{ //… };
继承的好处
节省重复代码的开销
例子
未使用继承
class person { protected: char name[10]; int age; char sex; public: void print(); }; class employee{ protected: char name[10]; int age; char sex; char department[20]; float salary; public: print(); };
使用继承
//定义一个基类 class person{ protected: char name[10]; int age; char sex; public: //…… }; //定义一个派生类 class employee:public person { protected: char department[20]; float salary; public: //…… };
声明
class 派生类名:继承方式 基类名 { //派生类新增的数据成员和成员函数 };
访问
基类成员在派生类中的访问属性
按访问属性归纳
基类中访问属性
继承方式
派生类中访问属性
private
public
不可访问
private
不可访问
protected
不可访问
原先是私有访问权限 无论哪种继承都无法访问
public
public
public
private
private
protected
protected
原先是公有访问权限 权限会对应于继承方式发生改变
protected
public
protected
private
private
protected
protected
原先为保护访问权限 只有公有继承仍然保持保护权限 其他会根据继承方式改变为对应权限
按继承方式归纳
继承方式
基类访问属性
派生类访问属性
public
public
public
protected
protected
private
不可访问
private
public
private
protected
private
private
不可访问
protected
public
protected
protected
protected
private
不可访问
总结
原先访问属性为私有权限的成员,无论那种继承方式都不可访问
公有继承,是最符合直觉的继承方式,它保持了允许继承的成员的原有访问属性。
私有继承与保护继承,会将允许继承的权限强行修改为对应继承方式的权限
对基类成员的访问形式
内部访问
由派生类中新增成员对基类继承来的成员的访问。
外部访问
在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
继承方式会影响这两种访问的控制权限
私有继承的访问
规则
原先基类中的非private成员被继承后,都变为private权限。
原先基类中的private成员不可被访问,但可以通过基类的public成员函数来间接访问它们
派生类内部的其他成员可以直接访问从基类继承的成员
类外通过派生类的对象则无法访问
例子
class Point { //基类Point类的定义 public: //公有函数成员 void InitP(float x = 0, float y = 0) { this->X = x; this->Y = y;} void Move(float offX, float offY) { X += offX; Y += offY; } float GetX() const { return X; } float GetY() const { return Y; } private: //私有数据成员 float X, Y; }; class Rectangle: private Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) {InitP(x,y);W=w;H=h;} //访问基类公有成员 void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX( )const {return Point::GetX( );} float GetY( )const {return Point::GetY( );} float GetH( )const {return H;} float GetW( )const {return W;} private: //新增私有数据 float W,H; }; #include<iostream.h> #include<math.h> int main( ) { //通过派生类对象只能访问本类成员 Rectangle rect; //定义Rectangle类的对象 rect.InitR(2,3,20,10); //设置矩形的数据 rect.Move(3,2); //移动矩形位置 cout<<rect.GetX( )<<',‘//输出矩形的特征参数 <<rect.GetY( )<<',' <<rect.GetH( )<<',' <<rect.GetW( )<<endl; return 0; }
说明
私有继承后,基类所有被继承过来的成员(private除外)都被改为私有权限
私有继承后的派生类,再被别的类继承,将得不到任何可继承的成员
“绝育”
公有继承的访问
规则
原先基类中的非private成员被继承后,权限不发生改变
原先基类中的private成员不可被访问,但可以通过基类的public成员函数来间接访问它们
派生类内部的其他成员可以直接访问从基类继承的成员
类外通过派生类的对象只能访问原先基类继承过来的公有权限成员
例子
class Point //基类Point类的声明 {public: //公有函数成员 void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX( ) {return X;} float GetY( ) {return Y;} private: //私有数据成员 float X,Y; protected: //受保护数据成员 int a,b; }; class Rectangle: public Point //派生类声明 { public: //新增公有函数成员 void InitR(float x, float y, float w, float h) {InitP(x,y);W=w;H=h;}//调用基类公有成员函数 float GetH( )const {return H;} float GetW( )const {return W;} private: //新增私有数据成员 float W,H; }; #include<iostream.h> #include<math.h>int main( ) { Rectangle rect; rect.InitR(2,3,20,10); //通过派生类对象访问基类公有成员 rect.Move(3,2); cout<< <<',' << <<',' <<rect.GetH( )<<',' <<rect.GetW( )<<endl; return 0; }
说明
尽管公有继承,不改变原先基类成员的访问属性,但在遇到私有成员时,还是只继承但不可访问,只能通过基类的公有成员函数错位接口去访问。
可以在公有继承的概念中去理解保护属性
与private属性相似,protected属性也是一种类内可访问,类外不可访问的权限
不同之处在于,protected属性的成员在被派生类公有继承后,仍然可以在派生类中实现类内访问,仍然拒绝类外访问。而private属性的成员则在派生类中也无法实现类内访问。因此protected属性,使得封装更灵活。
公有继承后,派生类的对象可以作为基类的对象来使用
派生的对象可以赋给基类的对象
(约定derived是从类base公有派生而来的) derived d; base b; b=d;
派生类的对象可以初始化基类的引用
derive d; base &br=d;
派生类对象的地址可以赋给指向基类的指针
derived d; base *pb=&d;
通过指针或引用只能访问 对象d中所继承的基类成员
例子
class base{ public: test1(){cout << “test1:base”<<endl;} }; class derived : public base { public: test2(){cout << test2:derived”<<endl;} }; void main() { derived d; base &b = d; b.test2(); b.test1(); }
保护继承的访问
规则
原先基类中的非private成员被继承后,权限变为protected
原先基类中的private成员不可被访问,但可以通过基类的public成员函数来间接访问它们
派生类内部的其他成员可以直接访问从基类继承的成员
类外通过派生类的对象无法访问原先基类继承过来的任何成员
例子
class Rectangle: protected Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) {InitP(x,y);W=w;H=h;} //访问基类公有成员 void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX( ) {return Point::GetX( );} float GetY( ) {return Point::GetY( );} float GetH( ) {return H;} float GetW( ) {return W;} private: //新增私有数据 float W,H; };
说明
protected 成员的特点与作用
对建立其所在类对象的模块来说(水平访问时),它与 private 成员的性质相同。
对于其派生类来说(垂直访问时),它与 public 成员的性质相同。
派生类修改基类
修改基类成员
在派生类中声明了一个与基类成员同名的新成员
在派生类作用域内或者在类外通过派生类的对象直接使用这个成员名,只能访问到派生类中声明的同名新成员
新成员覆盖了从基类继承的同名成员,称为同名覆盖
描述新的特征或方法
成员函数重载
成员函数重写
同名覆盖
继承的访问控制
派生类继承了基类中除构造函数和析构函数之外的所有成员。
派生类的成员组成
继承基类的成员
派生类定义时声明的成员;
可执行的操作
可以增加新的数据成员;
可以增加新的成员函数;
可以重新定义基类中已有的成员函数;
可以改变现有成员的属性。
派生类与基类的关系
派生类是基类的具体化
类的层次通常反映了客观世界中某种真实的模型。如,定义输入设备为基类,而键盘、鼠标和数字化板将是派生类。
派生类是基类定义的延续
虚基类
虚函数
派生类是基类的组合
在多继承时,一个派生类有多于一个的基类, 这时派生类将是所有基类行为的组合。
派生类的构造函数和析构函数
不可继承
基类的构造函数和析构函数不能被继承,一般派生类要加入自己的构造函数。
执行顺序
当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;
调用基类的构造函数;
调用内嵌对象成员(子对象类)的构造函数(有多个对象成员时,调用顺序由它们在类中声明的顺序确定);
派生类的构造函数体中的内容
当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
规则
基类的构造函数没有参数,或没有显式定义构造函数时
派生类可以不向基类传递参数,甚至可以不定义构造函数;
当基类含有带参数的构造函数时,派生类必须定义构造函数
派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
格式
派生类名(参数总表):基类名(参数表) { // 派生类新增成员的初始化语句 }
基类构造函数的参数,通常来源于派生类构造函数参数总表,也可以用常量值
例子
#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; }
说明
当基类构造函数不带参数时,派生类可不定义构造函数,但基类构造函数带有参数,则派生类必须定义构造函数。
若基类使用缺省构造函数或不带参数的构造函数,则在派生类中定义构造函数时可略去“:基类构造函数名(参数表)”
如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类的构造,依次上溯。
由于析构函数是不带参数的,在派生类中是否定义析构函数与它所属的基类无关,基类的析构函数不会因为派生类没有析构函数而得不到执行,基类和派生类的析构函数是各自独立的。
注意事项
派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有缺省的构造函数或者根本没有定义构造函数
当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数途径。
多继承
多继承
定义
派生类从多个基类派生。 区别于单继承,派生类只从一个基类派生。
声明
class 派生类名:继承方式1 基类名1,…,继承方式n 基类名n{ // 派生类新增的数据成员和成员函数 };
Class z:public x,y{ //类z公有继承了类 x,私有继承了类y //… }; class z:x,public y{ //类z私有继承了类x,公有继承了类y //… }; class z:public x,public y{ //类z公有继承了类x和类y //… };
访问·
对基类成员的访问必须是无二义性
class X{ public: int f(); }; class Y{ public: int f(); int g(); }; class Z:public X,public Y { public: int g(); int h(); }; Z obj; obj.f(); //二义性错误,不知调用的是类X的f(),还是类Y的f()。
使用类名限定可以消除二义性
obj.X∷f(); //调用类X的f() obj.Y∷f(); //调用类Y的f()
例子
#include<iostream.h> class A { public: void setA(int x) { a=x; } void printA() { cout<<"a="<<a<<endl; } private: int a; }; class B { public: void setB(int x) { b=x; } void printB() { cout<<"b="<<b<<endl; } private: int b; }; class C:public A,private B { public: void setC(int x,int y) { c=x; setB(y); } void printC() { printB(); cout<<"c="<<c<<endl; } private: int c; }; void main() { C obj; obj.setA(3); obj.printA(); obj.setB(4);//错误 obj.printB();//错误 obj.setC(6,8); obj.printC(); } a=3 b=8 c=6
构造函数与析构函数
构造函数格式
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n) { // 派生类新增成员的初始化语句 }
执行顺序
先执行基类构造函数,再执行对象成员的构造函数,最后执行派生类构造函数。
必须同时负责该派生类所有基类构造函数的调用。派生类的参数个数必须包含完成所有基类初始化所需的参数个数。
处于同一层次各基类构造函数执行顺序,取决于声明派生类时所指定各基类的顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序无关。
多重派生
定义
由一个基类派生出多个不同的派生类。
多层派生
定义
派生类又作为基类,继续派生新的类。
虚基类
引入原因
当某一个类的多个直接基类是从另一个共同基类派生而来时,这些直接基类中从上一级基类继承来的成员就拥有相同的名称。在派生类的对象中,这些同名成员在内存中同时拥有多个拷贝。如何进行分辨呢?
使用作用域标示符来唯一表示它们。
#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
定义虚基类,使派生类中只保留一份拷贝。
#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;} }; class derived:public base1,public base2{ public: derived( ){ cout<<"derived a="<<a<<endl;} }; main( ) { derived obj; return 0; } 程序运行结果如下:: base a=5 base1 a=15 base2 a=35 derived a=35
声明格式
虚基类的声明: 位置:定义派生类时声明。 其语法形式如下: class 派生类名:virtual 继承方式 类名{ //… }
关键字virtual与继承方式关键字(public或private)的先后顺序无关紧要,它只说明是“虚拟继承”。下面二个虚继承方法是等价
class derived:virtual public base{ //… }; class derived:public virtual base{ //… };
注意事项
如果在虚基类中定义有带形参的构造函数,并且没有定义缺省形参的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。
C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用,如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。
建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略。
为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这次虚基类构造函数必须只被调用一次。由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最(远)派生类。
从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数的调用。但是,只有用于建立对象的那个最远派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的子对象只初始化一次。
若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数;
C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。
对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下;
对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下;
若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。
class X:public Y,virtual public Z {//….}; X one;
定义类X的对象one后,将产生如下的调用次序 Z(); Y(); X();
一个基类在作为某些派生类虚基类的同时,又作为另一些派生类的非虚基类,这种情况是允许的。下例说明了这个问题。
class B { //… }; class X:virtual public B { //… }; class Y:virtual public B { //… }; class Z:public B { //… }; class AA:public X,public Y,public Z { //… };
构造函数
#include<iostream.h> class base { public: base(int sa) { a=sa; cout<<"Constructing base"<<endl; } private: int a; }; class base1:virtual public base{ public: base1(int sa,int sb):base(sa) { b=sb; cout<<"Constructing baes1"<<endl; } private: int b; }; class base2:virtual public base{ public: base2(int sa,int sc):base(sa) { c=sc; cout<<"Constructing baes2"<<endl; } private: int c; }; class derived:public base1,public base2 { public: derived(int sa,int sb,int sc,int sd): base(sa),base1(sa,sb),base2(sa,sc) { d=sd; cout<<"Constructing derived"<<endl; } private: int d; }; main() { derived obj(2,4,6,8); return 0; } 结果 Constructing base Constructing base1 Constructing base2 Constructing derived
赋值兼容规则
定义
在需要基类对象的任何地方都可以使用公有派生类的对象来替代
一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止
公有派生类实际上就具备了基类的所有特性,凡基类能解决的问题,公有派生类也能解决。
场景
派生类的对象可以被赋值给基类对象。
Base b; Derived d; b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
派生类的对象可以初始化基类的引用。
Derived d; Base &br=d;
指向基类的指针也可以指向派生类
通过基类对象名、指针只能使用从基类继承的成员
Derived d; Base *bptr=&d;
派生类对象的地址赋值给指向基类的指针
Derived *dptr; Base *bptr=dptr;
派生类对象的指针赋值给指向基类对象的指针
例子
class Base{ … }; class Derived:public Base{ … };
根据赋值兼容规则,在基类Base的对象可以使用的任何地方,都可以用派生类derive的对象来替代,但只能使用从基类继承来的成员。
例4.17 赋值兼容规则实例。 #include <iostream.h> class base{ public: int i; base(int x) {i=x;} void show() {cout<<"base "<<i<<endl;} }; class derive:public base{ public: derive(int x):base(x) { }; void show() {cout<<"derive "<<i<<endl;} }; void main() { base b1(11); b1.show(); derive d1(22); b1=d1; b1.show(); derive d2(33); base &b2=d2; b2.show(); derive d3(44); base *b3=&d3; b3->show(); derive *d4=new derive(55); base *b4=d4; b4->show(); delete d4; } 结果 Base 11 Base 22 Base 33 Base 44 Base 55
说明
声明为指向基类对象的指针可以指向它的公有派生的对象,但不允许指向它的私有派生的对象。
class base{ //… }; class derive:private base { //… }; void main() { base op1,*ptr; derive op2; ptr=&op1; ptr=&op2; //错误,不允许指向它的私有派生类对象 //… }
允许将一个声明为指向基类的指针指向其公有派生类的对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。
#include<iostream.h> class Base{ //… }; class Derived:public Base{ //… }; void main() { Base obj1; Derived obj2,*ptr; ptr=&obj2; ptr=&obj1;// 错误,将派生类指针指向基类对象 //… }
声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类中定义的成员
class A { //… public: void print1(); }; class B:public A { //… public: print2(); }; void main() { A op1,*ptr; // 定义基类A的对象op1和基类指针ptr B op2; // 定义派生类B的对象op2 ptr=&op1; // 将指针ptr指向基类对象op1 ptrprint1(); // 调用基类函数print1() ptr=&op2; // 将指针ptr指向派生类对象op2 ptrprint1(); // 调用对象op2从其基类继承来的成员函数print1() ptrprint2(); // 错误,基类指针ptr不能访问派生类中定义 的成员函数print2()
若想访问其公有派生类的特定成员| 可以将基类指针用显示类型转换为派生类指针。
((B*)ptr)-> print2();