200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > C++基本概念复习之二:多重继承 虚继承 纯虚函数(抽象类)

C++基本概念复习之二:多重继承 虚继承 纯虚函数(抽象类)

时间:2023-03-02 22:51:40

相关推荐

C++基本概念复习之二:多重继承 虚继承 纯虚函数(抽象类)

一、多重继承:

#include <iostream>

using namespace std;

class Horse

{

public:

Horse(){cout<<"Horse constructor...";}

virtual ~Horse(){cout<<"Horse destructor...";}

virtual void whinny()const{cout<<"Whinny!..";}

private:

int itsAge;

};

class Bird

{

public:

Bird(){cout<<"Bird constructor...";}

virtual ~Bird(){cout<<"Bird destructor...";}

virtual void chirp()const{cout<<"Chirp!...";}

virtual void fly(){cout<<"Fly!!!";}

private:

int itsWeight;

};

class Pegasus:public Horse,public Bird

{

public:

Pegasus(){cout<<"Pegasus constructor...";}

//飞马也以鸟鸣的方式叫,但叫出来的声音是马的声音

//注意const不能少,否则就不能覆盖父类中的虚方法

//void chirp()const,从而多态调用失败

//掉了const导致的后果是隐藏,将来通过子类

//对象的指针、引用或者子类对象本身,都无法调用父类的

//同名方法,另一方面,通过指向子类对象的父类类型的指针

//也不能达到动态绑定从而多态调用,调用的永远是父类的

//方法

virtual void chirp()const{whinny();}

virtual ~Pegasus(){cout<<"Pegasu destructor...";}

};

int main()

{

Horse * ranch[2];

Bird * aviary[2];//鸟笼和农场的容量都是2

//观察打印的每一行可以看出每个对象的创建过程

ranch[0]=new Horse;//初始化农场

cout<<endl;

ranch[1]=new Pegasus;

cout<<endl;

aviary[0]=new Bird;//初始化鸟笼

cout<<endl;

aviary[1]=new Pegasus;

cout<<endl;

cout<<"*********************"<<endl;

ranch[0]->whinny();cout<<endl;

ranch[1]->whinny();cout<<endl;

aviary[0]->chirp();aviary[0]->fly();cout<<endl;

aviary[1]->chirp();aviary[1]->fly();cout<<endl;

cout<<"*********************"<<endl;

//观察打印的每一行可以看出每个对象的销毁过程

delete ranch[0];//清理农场

cout<<endl;

delete ranch[1];

cout<<endl;

delete aviary[0];//清理鸟笼

cout<<endl;

delete aviary[1];

cout<<endl;

//上面的子类对象能得以正确析构,要归功于虚析构函数

return 0;

}

打印结果:

避免歧义:

上面的代码中,若Horse和Bird类中都有getColor方法(可能是虚方法,也可能不是),且该方法在两个类中的签名完全一致,对子类来说,这两个方法都继承了,问题也随之而来:

1、当我们在客户端使用子类对象来调用该方法时:

Pegasus pgs;

pgs.getColor();

会告诉错误,因为编译器不知道该调用哪个getColor。解决办法,要么让子类有自己的getColor方法(覆盖父类的getColor),我们可以在子类自己的getColor方法指定调用哪个父类的getColor,或者都不调用,子类的getColor有自己全新的逻辑;要么在调用的时候指定调谁的:pgs.Bird::getColor()。

2、如果我们在客户端用这样调用:

Pegasus pgs;

Horse h=pgs;

h.getColor();

则不会有任何歧义,这种调用方式不会引入多态机制,永远调用父类的方法——与java不同。

3、如果这样调用:

Pegasus pgs;

Horse* ph=&pgs;//或者通过引用

ph->getColor();

也不会有任何歧义,这也不会引入多态机制,仅仅是在调用父类方法。

注意,上面所说的歧义,与getColor是不是虚方法没有关系,即,仅仅靠虚方法不能避免歧义,解决办法与1类似。

另一种歧义:

现在的情况是:马和鸟都继承自动物类Animal,由于飞马从马和鸟多继承,导致飞马对象的内存结构是这样的:

这五块内容,构成一个完整的飞马Pegasu对象,其中有两块一模一样的数据:Animal。这是飞马的两个直接父类从共同的基类Animal继承而来的。代码如下:

#include <iostream>

using namespace std;

enum COLOR{BLACK,RED,BLUE};

class Animal

{

public:

void baseFunc(){cout<<"Animal func..."<<endl;}

};

class Horse:public Animal

{

};

class Bird:public Animal

{

};

class Pegasus:public Horse,public Bird

{

};

int main()

{

Pegasus pgs;

Horse* ph=&pgs;

ph->baseFunc();//无歧义

pgs.baseFunc();//有歧义

return 0;

}

二、虚继承解决歧义:

虚继承得到的最终子类对象的内存模型:

从而能消除上面引起的歧义。代码:

#include <iostream>

using namespace std;

enum COLOR{BLACK,RED,BLUE};

class Animal

{

public:

void baseFunc(){cout<<"Animal func..."<<endl;}

};

class Horse:virtual public Animal

{

};

class Bird:virtual public Animal

{

};

class Pegasus:public Horse,public Bird

{

};

int main()

{

Pegasus pgs;

Horse* ph=&pgs;

ph->baseFunc();//无歧义

pgs.baseFunc();//无歧义,虚继承解决歧义

return 0;

}

虚继承引进的一个注意事项是:共同基类(这里指Animal)的初始化任务,由最终子类完成。——上述代码没有体现这一点,但要注意。所谓的“类的初始化”这种说法,严格来说,并不确切。对于上面的内存模型,它是一个最终子类对象的内存模型,即是一个Pegasu对象,我们给其所占内存里的内容编上号,从上到下从左至右,分别为0,1,2,3:

可以这样理解:整个(0,1,2,3)构成的是一个完整的飞马对象,而0块内容是一个Animal对象,1和0是一个Horse对象,2和0是一个Bird对象。上面所说的“基类的初始化”,意指0块内容的初始化。不涉及虚继承的情况下,父类的初始化,都在直接子类完成——通过在子类构造的参数列表后面手动调用父类相应的构造来完成。

经验总结:在单继承可行的情况,不要使用多继承,避免带来的开销及额外的风险。

三、纯虚函数(抽象类):

有纯虚函数的类是抽象类。抽象类不能实例化,它存在的意义类似java中的接口和抽象类,将后代共同的操作统一声明,至于具体如何实现,后代自己决定。一旦在父类中定义一个纯虚函数,就对子类提出这样的要求:要想使子类不是抽象类,就必须要覆盖实现这个纯虚函数,要覆盖父类中的每个纯虚函数。

虚函数可以有自己的实现。往往它完成的是通用的、基本的工作,通常的做法是:在子类具体的覆盖实现中,调用父类纯虚函数的实现来完成共同的基本操作。其实虚函数也能做到这一点,不过父类必须给出虚函数实现,而不能仅仅是声明。从抽象的角度来讲,具有纯虚函数的类抽象程度更大一些,它仅有通用方法的声明即可,且不能被实例化。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。