《C++继承篇》之多继承、多重继承与虚继承
在学习过程中,知识点的总结都是由发现问题到解决问题的过程中对知识理解和认识的升华。闲话少叙,下面围绕具体的栗子详细介绍C++学习之继承篇(多继承、多重继承与虚继承) 目录:
1-1多继承
基础知识:多继承是指一个子类继承多个父类。多继承对父类的个数没有限制,继承方式可以是公共继承、保护继承和私有继承。
不写继承方式,则默认是private继承。
直接给出多继承的模板格式太过死板,也不利于读者理解,下面通过一个栗子来引出多继承的格式:
CRundrect类多继承父类CRectangle、CCircle,给出参数长、宽、半径,求矩形和圆形面积之差
1-2多继承代码
#include<iostream>
#include<string>
using namespace std;
class CCircle
{
public:
double dbRadius;
CCircle(double radius) {
dbRadius=radius;
}
double GetRadius(){
return dbRadius;
}
double Area1(){
return 3.141592*dbRadius*dbRadius;
}
};
class CRectangle
{
public:
double dbWith,dbHeight;
CRectangle(double width,double height) {
dbWith=width;
dbHeight=height;
}
double GetWidth(){
return dbWith;
}
double Getheight(){
return dbHeight;
}
double Area2(){
return dbWith*dbHeight;
}
};
class CRundrect: public CCircle,public CRectangle
{
public:
CRundrect(double width,double height,double radius): CCircle(radius), CRectangle(width,height)
{ }
// 到这里多继承的格式定义应该就明确了,同时在构造函数中要注意对父类构造函数的继承
double Area(){
return CRectangle::Area2()-CCircle::Area1();
}
};
void fn(CRundrect &c);
int main()
{
CRundrect rundrect(200.0, 15.0, 10.0);
fn(rundrect);
return 0;
}
void fn(CRundrect &c)
{
cout<<"圆形的面积为:"<<c.Area1()<<endl;
cout<<"矩形的面积为:"<<c.Area2()<<endl;
cout << "矩形减去圆形后的面积:" << c.Area() << endl;
// 从这里可以看出,子类继承的是父类的数据成员和成员函数,是可以直接使用的
}
运行结果如下:
同时要指出:
在多继承中,任何父类的指针都可以指向子类的对象,在实例化子类时,先根据继承的顺序依次调用父类的构造函数,然后再调用该子类自己的构造函数;
用delete销毁该基类对象时,如果该基类的析构函数不是虚析构函数,且该基类的继承顺序在第一个,如 class ChildLabourer :public Worker,public Children 那么delete 父类的时候只会调用父类Worker的析构函数,系统不会出错,但是如果继承的时候顺序不是在第一位(class ChildLabourer :public Children,public Worker),就会报内存泄露的错误,如果父类的析构函数是虚析构函数,那么销毁的时候会先调用子类的析构函数再调用所有父类的析构函数,注意,此时,子类的父类的析构函数都会被调用!
2-1多重继承
多重继承特点总结如下:
(1)多重继承与多继承不同,当B类从A类派生,C类从B类派生,此时称为多重继承
(2)当实例化子类时,会首先依次调用所有基类的构造函数,最后调用该子类的构造函数;销毁该子类时,则相反,先调用该子类的析构函数,再依次调用所有基类的析构函数。
(3)无论继承的层级有多少层,只要它们保持着直接或间接的继承关系,那么子类都可以与其直接父类或间接父类构成is a关系,并且能够通过父类的指针对直接子类或间接子类进行相应的操作,子类对象可以给直接父类或间接父类的对象或引用赋值或初始化。
ps:我们C++老师也把类的多重继承称为儿子类、父亲类、爷爷类、祖宗类,生动形象,也便于初学者理解。
2-2多重继承代码
上述栗子的升级版:
引入爷爷类CShape,判断输入的两个数作为长和宽是长方形还是正方形。
#include<iostream>
#include<string>
using namespace std;
class CShape
{
public:
double x,y;
CShape(double x1,double y1):x(x1),y(y1){}
double GetX(){
return x;
}
double GetY(){
return y;
}
virtual double Area()=0;
};
class CRectangle: public CShape
{
public:
double dbWith,dbHeight;
CRectangle(double width,double height,double x,double y): CShape(x,y) {
dbWith=width;
dbHeight=height;
}
double GetWidth(){
return dbWith;
}
double Getheight(){
return dbHeight;
}
double Area(){
return dbWith*dbHeight;
}
};
class square: public CRectangle
{
public:
square(double width,double height,double x,double y): CRectangle(width,height,x,y)
{ }
int ifsquare(){
if(CRectangle::dbWith==CRectangle::dbHeight)
{
cout << "\n定位坐标:(" << CRectangle::GetX() << "," << CRectangle::GetY() <<")"<< endl;
cout<<"this is a square"<<endl;
}
else {
cout << "\n定位坐标:(" << CRectangle::GetX() << "," << CRectangle::GetY() <<")"<< endl;
cout<<"this is a rectangle"; }
return 0;
}
};
int main()
{
cout<<"input length and width:"<<endl;
double length,width;
cin>>length>>width;
square Square(length, width, 15.0, 15.0);
Square.ifsquare();
return 0;
}
运行结果:
3-1虚继承
我更愿意把虚继承看做多重继承的升级版
上面的这种继承属于菱形继承,如果实例化对象D 那么D—>B—>A D—>C—>A , 此时D将会从A得到两个相同的数据,这属于数据冗余,是不能容忍的。
那么菱形继承 需要用 虚继承 来解决数据冗余的问题,格式如下图所示:
用上述例子的终极版理解虚继承:
(读者在可尝试自行思考、编写本题,然后与后面的代码分析比较,效果更佳)
3-2虚继承代码
#include<iostream>
#include<string>
using namespace std;
class CShape
{
public:
double x,y;
CShape(double x1,double y1):x(x1),y(y1){} // 构造函数的另一种形式
double GetX(){
return x;
}
double GetY(){
return y;
}
virtual double Area()=0; // 纯虚函数
};
class CCircle:virtual public CShape // 定义为虚基类
{
public:
double dbRadius;
CCircle(double radius,double x,double y): CShape(x,y) {
dbRadius=radius;
}
double GetRadius(){
return dbRadius;
}
double Area(){
return 3.141592*dbRadius*dbRadius;
}
};
class CRectangle:virtual public CShape // 同样是定义虚基类
{
public:
double dbWith,dbHeight;
CRectangle(double width,double height,double x,double y): CShape(x,y) {
dbWith=width;
dbHeight=height;
}
double GetWidth(){
return dbWith;
}
double Getheight(){
return dbHeight;
}
double Area(){
return dbWith*dbHeight;
}
};
class CRundrect: public CCircle,public CRectangle
{
public:
CRundrect(double width,double height,double radius,double x,double y): CCircle(radius,x,y), CRectangle(width,height,x,y) ,CShape(x,y)
{ }
double Area(){
return CRectangle::Area()-CCircle::Area();
}
};
void fn(CShape &c);
int main()
{
CCircle circle(10.0,6.7, 8.7);
CRectangle rect(200.0,15.0, 9.7, 10.7);
fn(circle);
fn(rect);
CRundrect rundrect(200.0, 15.0, 10.0, 9.7, 10.7);
fn(rundrect);
return 0;
}
void fn(CShape &shape)
{
cout << "定位坐标:" << shape.GetX() << "," << shape.GetY() << endl;
cout << "面积:" << shape.Area() << endl;
}
运行结果:
开始如果没有定义虚基类的话,编译时会出现错误:
[Error] type 'CShape' is not a direct base of 'CRundrect'
当然在你理解虚继承之后这些问题都迎刃而解了。