白话C++系列(26) -- 接口类
2016-06-20 20:27 Keiven_LY 阅读(889) 评论(0) 编辑 收藏 举报接口类
问题:什么是接口类??
如果在一个抽象类中,仅含有纯虚函数,而不含有其他的任何东西,那么我们就称之为接口类。即:仅含有纯虚函数的类称为接口类。
那么,我们如何理解接口类的定义呢?
也就是说,在类当中,没有任何的数据成员,只有成员函数,而这仅有的成员函数当中,其又都是纯虚函数,此时,我们就把这样的类称之为接口类。
下面通过一个例子来说明接口类的定义方法。如下:
Shape这个类在之前的课程中已经讲过,此时,如果我们将Shape类中的计算面积和计算周长这两个成员函数都定义成纯虚函数,并且Shape类此时还不含有别的成员函数以及数据成员,那么,此时我们就称Shape这个类为接口类。
在实际的使用过程中,接口类更多的是用来表达一种能力或协议。这句话又该如何理解呢?我们还是通过一个例子来进行说明。
比如,我们有上面的一个类,这个类的意思就是“会飞的”。如果我们要是有会飞这种能力,那么我们就应该事先以下两个函数:起飞和降落。在这里,我们可以看到,起飞和降落这两个函数都是虚函数,那么继承Flyable这个类的子类就必须要实现在Flyable这个类当中所定义的起飞和降落这两个纯虚函数,实现之后,它就具有了“会飞”这种能力。我们来看一下:如果我们定义了鸟(Bird)这个类,并且Bird去继承Flyable这个类(如下)。
当形成了这种继承关系之后,如果我们要实例化Bird,那么我们就必须要在Bird这个类当中去实现起飞和降落这两个函数(如上面省略号表示的函数体)。大家可以想一想,如果我们在使用的时候,有如下一个函数flyMatch()。
我们看到,flyMatch这个函数所要求传入的指针是“会飞”的,也就是说,任何会飞的对象的指针都可以传入进来。Bird这个类实现了Flyable,即Bird是一个子类。前面我们讲过,当我们用一个子类去继承父类的时候,就形成了一种is-a的关系。当形成了这种is-a的关系之后,我们就可以在flyMatch,也就是飞行比赛当中传入两个指针,这两个指针要求传入的类只要是Flyable的子类就可以了。那么这个时候,我们知道Bird是Flyable的子类,那么在flyMatch中就可以传入Bird类的对象指针。传入进来的对象指针就可以调用Flyable类中所要求必须实现的起飞和降落这两个函数了。这个时候,大家应该隐隐的感觉到,其实Flyable这个类就相当于是一种协议,你如果想要参加飞行比赛,那么你就一定要会飞;那么如果你会飞,你一定实现了起飞和降落这两个函数;那么你实现了这两个函数,那么我们就可以再flyMatch(飞行比赛)中去调用了。同样的道理,如果我们有如下一个类,这个类叫做CanShot(能够射击,即一种具有射击的能力)。
在这个类当中,我们定义了两个纯虚函数:瞄准和装弹。此时,如果我们再定义一个Plane(飞机类),飞机可以进行多继承,其继承了Flyable(会飞的)和CanShot(可以射击的)这两个类,如下所示:
那么这个时候,想要实例化Plane,那么,它就要必须实现Flyable中的起飞(takeoff)和降落(land)以及CanShot中的瞄准(aim)和装弹(reload)。如果我们把这些都实现了,那么,假设我们有如下一个函数fight(战斗)。战斗的时候,我们要求,只需要具有能够射击这种能力就可以了,如下所示:
那么,作为Plane(飞机)这个二类来说,它既是Flyable的子类也是CanShot(射击)这个类的子类。Fight这个函数要求只要是CanShot的子类就可以了。如果我们此时传入Plane这个类的对象指针给fight,其就是满足fight函数参数要求的。那么传入进来的对象指针必定是CanShot这个类的对象指针,呢么它就一定实现了瞄准和装弹这两个函数,实现之后,我们就可以再fight战术中去调用者两个函数了。
对于接口类来说,更为复杂的情况如下所示:
当我们定义一个Plane(飞机)这样的一个类的时候,对于飞机来说,他一定是能够会飞的,所以我们继承Flyable这个类,这样飞机就有了好“会飞”的能力。如果想要去实例化飞机,,那么此时我们就必须要实现起飞和降落这两个函数。而战斗机是可以继承飞机的,同时战斗机还应该具有射击的能力,这个时候它也是一种多继承,如下所示:
这种多继承请大家注意:它的第一个父类(Plane)并不是一个接口类,它的第二个父类(CanShot)则是一个接口类。这种情况下,我们从逻辑上可以理解为:战斗机是继承了飞机的绝大部分属性,同时还具有能够射击这样的功能,那么我们就需要在战斗机中去实现CanShot这个类当中的瞄准和装弹这两个函数。实现完成之后,如果我们有一个函数airBattle(空战),而空战的时候就需要传入两个战斗机的对象指针,如下所示:
因为此时我们传入的是战斗机的对象指针,那么战斗机对象当中一定实现了CanShot中瞄准和装弹这两个函数,同时,也肯定实现了Flyable中的起飞和降落这两个函数,于是我们就可以放心地在airBattle(空战)这个函数中去调用Flyable和CanShot所约定的函数了。
接口类代码实践
题目描述:
/* ******************************************** */
/* 接口类
1. Flyable类,成员函数:takeoff(起飞)、land(降落)
2. Plane类,成员函数:takeoff、land、printCode,数据成员:m_strCode
3. FighterPlane类,成员函数:构造函数、takeoff、land
4. 全局函数flyMatch(Flyable *f1, Flyable *f2)
*/
/* ******************************************** */
程序框架:
头文件(Flyable.h)
#ifndef FLYABLE_H #define FLYABLE_H //Flyable只含有纯虚函数,没有其他的成员函数,也没有任何的数据成员, //所以不需要.cpp文件,这样的类就称之为接口类 class Flyable { public: virtual void takeoff() = 0; virtual void land() = 0; }; #endif
头文件(Plane.h)
#ifndef PLANE_H #define PLANE_H #include <string> #include "Flyable.h" using namespace std; class Plane:public Flyable { public: Plane(string code); virtual void takeoff(); virtual void land(); void printCode(); private: string m_strCode; }; #endif
源程序(Plane.cpp)
#include <iostream> #include "Plane.h" using namespace std; Plane::Plane(string code) { m_strCode = code; } void Plane::takeoff() { cout << "Plane --> takeoff()" << endl; } void Plane::land() { cout << "Plane --> land()" << endl; } void Plane::printCode() { cout << m_strCode << endl; }
头文件(FighterPlane.h)
#ifndef FIGHTERPLANE_H #define FIGHTERPLANE_H #include "Plane.h" #include <string> using namespace std; class FighterPlane:public Plane { public: FighterPlane(string code); virtual void takeoff(); virtual void land(); }; #endif
源程序(FighterPlane.cpp)
#include <iostream> #include "FighterPlane.h" using namespace std; FighterPlane::FighterPlane(string code):Plane(code) { } void FighterPlane::takeoff() { cout << "FighterPlane --> takeoff()" << endl; } void FighterPlane::land() { cout << "FighterPlane --> land()" << endl; }
主调程序(demo.cpp)
#include <iostream> #include "stdlib.h" #include <string> #include "FighterPlane.h" using namespace std; void flyMatch(Flyable *f1, Flyable *f2) { f1->takeoff(); f1->land(); f2->takeoff(); f2->land(); } int main() { Plane p1("001"); Plane p2("002"); p1.printCode(); p2.printCode(); flyMatch(&p1, &p2); system("pause"); return 0; }
我们来看一看运行结果(按F5)
从运行结果,我们可以看到,首先打印出来两行”001”和”002”,这个毫无疑问是通过printCode函数打印出来的;接下来打印出来的四行,分别是f1的起飞和降落,f2的起飞和降落,这个也说明了Plane可以正确的作为参数传递给flyMatch。通过打印结果,我们可以看到,对于flyMatch这个函数来说,它相当于限制了传入参数的参数类型,并且可以在函数体中放心地去调用接口类当中所定义的纯虚函数,这个就是接口类最为常见的用法。接下来我们使用FighterPlane这个类来试一试,看看其能不能作为参数传入到flyMatch当中,修改main函数如下:
int main() { FighterPlane p1("001"); FighterPlane p2("002"); p1.printCode(); p2.printCode(); flyMatch(&p1, &p2); system("pause"); return 0; }
我们来看一看运行结果:
通过运行效果,我们可以看到,打印飞机编号跟之前是一样的,但后面四行是不一样的。我们可以看到,通过f1和f2调用起飞和降落函数呢,实际上调用的是FighterPlane(战斗机)这个类当中的起飞和降落函数。此外,我们再做一个小小的修改:要求在FighterPlane中不仅要继承Plane这个类,而且还要继承Flyable这个类,同时,不让Plane继承Flyable这个类(注意此时就要去掉之前实现的纯虚函数takeoff和land)。这个时候,我们发现此时是一个多继承:FighterPlane既继承了Plane这个类,也继承了Flyable这个类。在这种情况下,对于FighterPlane来说,它就有了两个父类,这就意味着:FighterPlane既是一个Plane,也是一个Flyable。
修改后的头文件(Plane.h)
#ifndef PLANE_H #define PLANE_H #include <string> #include "Flyable.h" using namespace std; class Plane { public: Plane(string code); void printCode(); private: string m_strCode; }; #endif
修改后的源程序(Plane.cpp)
#include <iostream> #include "Plane.h" using namespace std; Plane::Plane(string code) { m_strCode = code; } void Plane::printCode() { cout << m_strCode << endl; }
修改后的头文件(FighterPlane.h)
#ifndef FIGHTERPLANE_H #define FIGHTERPLANE_H #include "Plane.h" #include <string> using namespace std; class FighterPlane:public Plane,public Flyable { public: FighterPlane(string code); virtual void takeoff(); virtual void land(); }; #endif
main函数不变,如下:
int main() { FighterPlane p1("001"); FighterPlane p2("002"); p1.printCode(); p2.printCode(); flyMatch(&p1, &p2); system("pause"); return 0; }
此时我们来看一看运行结果:
从运行结果可以看到跟之前一样,但是意义却不同了。此时的FighterPlane既继承了Plane这个类,也继承了Flyable这个类。这就意味着,如果有另外一个函数,要求传入的是Plane而不是FighterPlane,如下:
void flyMatch(Plane *f1, Plane *f2) { f1->printCode(); f2->printCode(); }
而我们在main函数中:
int main() { FighterPlane p1("001"); FighterPlane p2("002"); flyMatch(&p1, &p2); system("pause"); return 0; }
我们实例化的是FighterPlane,但在flyMatch这个函数中,我们仍然传入的是FighterPlane(p1和p2),这样写是合法的,因为flyMatch要求传入的类是FighterPlane的父类,所以这样写是合法的。此时,我们按F5来看一看运行的结果如何:
我们可以看到,打印出来的结果只有两行”001”和”002”。通过这两个实验,我们可以更进一步的体会到接口类给我们带来的好处,以及多继承给编程带来的灵活性。