c++ 的接口类到底是干什么用的?什么叫接口?
打一个比方如抽象一个类"人":class people。人有一些必须要做的事,比如:吃饭、呼吸。
但是每个人吃饭又不一样,比如:用筷子吃(中国人)、用刀叉(欧美人)、用手抓(印度人)。
那么你如果不把“人”(people)这个类做为一个接口(抽象类)。吃就至少要写3个“吃”的重载方法。但一个中国人可能永远也用不到用刀叉吃饭、用手抓饭这样的方法。这就造成写“人”(people)这个类的时候写了两个多余的方法。
所以C++提供了一个叫接口(也就是抽象类)的东西,让你在声明一个“人”这个类的时候,就告诉大家:你们如果想继承我写的这个接口类(抽象类),就必须为“人”(people)这个类的子类写吃饭、呼吸的方法。这样,欧美的朋友继承这个类的时候,他们写“吃”的方法就会用刀叉。而印度的朋友继承这个类的时候,就会手抓。
也就是说c++没有所谓的接口,通过类实现面向对象的编程,而在基类中只给出纯虚函数的声明,然后通过在派生类中实现纯虚函数的具体定义的方式实现接口,不同派生类实现接口的方式也不尽相同,从而实现多态;其实无论c#还是Java,里面的interface的本质都是类。
现通过一个简单的实例讲解具体实现步骤。
定义、实现接口并将其导出为可用的动态库
首先创建动态链接库(DLL),文件->新建->项目->选择Visual C++ ->Windows桌面->动态链接库
先来区分一下静态库和动态库:
静态库 window下是 .lib格式 linux下是 .a格式 编译期引入到程序代码
动态库 window下是 .dll格式 linux下是 .so格式 运行时候进行链接
DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。
DLL有以下几个优点:
- 节省内存。同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。如果使用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)。
- 不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。事实上,很多软件都是以这种方式升级的。例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。
- Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。
类接口的定义(通常在头文件中完成类接口的定义) /InterfaceDefineAndRealize.h/
#ifndef INTERFACE_DEFINE_AND_REALIZE
#define INTERFACE_DEFINE_AND_REALIZE
#include <string>
using namespace std;
//define interface
class Person
{
public:
Person():m_StrName("###") {}; //成员列表初始化参数
virtual ~Person(){}; //析构函数
virtual void Eat()=0;//人需要吃东西
virtual void Sleep()=0;//人需要睡觉
virtual void SetName(const string strName)=0;//人都有名字
virtual string GetName()=0;//获取名字
virtual void Work()=0;//人可能要有工作
private:
string m_StrName;
};
//实现接口
//实现接口是通过派生类实现的,每个派生类依据自身特点,可以获取同一接口的不同实现
//也就是所谓的多态
class Chinese:public Person
{
public:
Chinese():m_strName("***")
{};
~Chinese()
{};
void Eat();
void Sleep();
void SetName(const string strName);
string GetName();
void Work();
private:
string m_strName;
};
#endif
#ifndef(if not define)的作用防止头文件的重复包含和编译
如定义
#ifndef x
#define x
…
#endif
它是宏定义的一种,可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等。实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种条件编译。
接口的实现 (通常在源文件中完成接口的实现)/InterfaceDefineAndRealize.cpp/
#include "InterfaceDefineAndRealize.h"
#include <iostream>
#include <string>
using namespace std;
//接口的外部实现
void Chinese::Sleep()
{
cout<<"Chinese sleep."<<endl;
}
void Chinese::Eat()
{
cout<<"Chinese eat."<<endl;
}
void Chinese::SetName(const string strName)
{
m_strName=strName;
}
void Chinese::Work()
{
cout<<"Chinese work."<<endl;
}
string Chinese::GetName()
{
return m_strName;
}
//需要导出的函数,即用户在外部可以调用的接口
_declspec(dllexport)bool GetPersonObject(void** _RtObject)
{
Person* pMan=NULL;
pMan=new Chinese();
*_RtObject=(void*)pMan;
return true;
}
接口的导出 通常在模块定义文件中完成 /InterfaceDefineAndRealize.def/
源文件下添加.def文件
LIBRARY InterfaceDefineAndRealize
EXPORTS
GetPersonObject
生成解决方案
可以在项目文件夹的Debug目录下看到dll和lib文件。lib是我们写在程序中添加的文件,dll是我们在生成程序之后要使用的文件。
测试DLL
新建一个控制台应用程序,加载上述三个文件,设置项目属性—>配置属性——>常规——>配置类型 ,选择"动态库.dlll",生成可用的动态库,假如项目名称为InterfaceDefineAndRealize(注意:项目名称必须与模块定义文件中 LIBRARY 后面定义的名字相同,否则将导致出现无法找到动态库的错误。),则在该项目的当前工作目录下位生成动态库和它的导入库。
接口的调用
为了与常规的调用动态库的方式保持一致,这里做一些额外工作。新建“include”文件夹,并将InterfaceDefineAndRealize.h放到此文件夹下,新建“lib”文件夹并将InterfaceDefineAndRealize.lib文件放到此文件夹下。新建项目UsingInterface,添加源文件实现调用接口的功能。
1)为项目添加附加包含目录
方法1:项目属性——>配置属性——>C/C++——>常规——>附加包含目录 将include文件夹的全路径添加进来。
方法2:项目属性——>配置属性——>VC++目录——>包含目录 中将include文件夹的全路径添加进来。
2)为项目添加附加库
方法1:项目属性——>配置属性——>链接器——>常规——>附加库目录 将lib文件夹的全路径添加进来。
方法2:项目属性——>配置属性——>VC++目录——>库目录 将lib文件夹的全路径添加进来。
注意:1)中的方法1与2)中的方法1对应,1)中的方法2与2)中的方法2对应,不能交换使用。
3)为项目添加导入库
项目属性——>配置属性——>链接器——>输入——>附加依赖项 中添加InterfaceDefineAndRealize.lib
4)为项目提供动态库
将生成的.dll动态库放到项目的当前目录下的Debug目录下,防止出现缺少动态库的错误。(没有这个目录就先生成解决方案,有了Debug目录后再放文件进来,一切准备好以后再重新生成解决方案)
5)编写代码,实现接口的调用
#include <iostream>
#include "InterfaceDefineAndRealize.h"
using namespace std;
bool _declspec(dllimport) GetPersonObject(void** _RtObject);
int main()
{
Person* person=NULL;
void* pObj=NULL;
if(GetPersonObject(&pObj))//调用接口
{
person=(Person*)pObj;
person->Eat();
person->Sleep();
person->SetName("yuan mw");
cout<<person->GetName()<<endl;
person->Work();
if(person!=NULL)
{
delete person;
person=NULL;
}
}
system("pause");
return 0;
}
运行结果
在此特别感谢CSDN的众位大侠:chuanshaoke、shimadear、无力吐槽的典哥等人