何为接口:
接口为一个特殊的类,其中只包含有方法、属性、索引器和事件的声明,但并没有其实现,若一个类实现了某个接口,则必须在类中包含接口中所有方法的实现代码。
为什么使用接口:
有看官可能要问了,这玩意只是声明了一下有啥用,我为什么不直接用类?那么,举一个例子(C#代码):
现在有一个类名为car,car中包含了一个Weight方法打印车重:
class car
{
public void Weight()
{
Console.WriteLine("输出车重");
}
}
接着创建一些子类,继承了car,然后分别重写Weight方法,新建一个carinformation,最后在main中输出三种车的重量:
class truck:car
{
public new void Weight()
{
Console.WriteLine("卡车车重10吨");
}
}
class Ferrari911:car
{
public new void Weight()
{
Console.WriteLine("法拉利911车重1.6吨");
}
}
class AudiQ7:car
{
public new void Weight()
{
Console.WriteLine("奥迪Q7车重2.2吨");
}
}
class carinformation
{
public void show(car cartype)
{
cartype.Weight();
}
}
public static void Main(string[] args)
{
carinformation carweight = new carinformation();
carweight.show(new truck());
carweight.show(new Ferrari911());
carweight.show(new AudiQ7());
}
一切都很完美,现在另一个人接受了这个任务,他想加入一辆新车VolkswagenCC,他是这样写的:
class VolkswagenCC:car
{
public new void CarWeight()
{
Console.WriteLine("大众CC车重1.6吨");
}
}
public static void Main(string[] args)
{
carinformation carweight = new carinformation();
carweight.show(new truck());
carweight.show(new Ferrari911());
carweight.show(new AudiQ7());
carweight.show(new VolkswagenCC());
}
结果并没有成功输出新车的车重,仔细看,问题就在于VolkswagenCC类中的方法不叫Weight而叫CarWeight,这个时候,一个统一的规则就十分必要。
接下来我把car这个类变为一个接口:
interface car
{
void Weight();
}
其他代码不变,可以看到这时原来的VolkswagenCC类是编译不过的,因为他没有对接口的函数进行实现,我想为什么要使用接口这个问题的答案应该显而易见了。
接口的C++与C#实现
C#实现
既然例子是用C#举的,那就先说C#吧。
//创建一个汽车接口
public interface car
{
void Weight();
}
//有四种车型,使用了汽车接口,都实现了车重方法,每种车还有自己的特有方法
public class truck : car
{
public void Weight()
{
Console.WriteLine("卡车车重10吨");
}
public void CarryingCapacity()
{
Console.WriteLine("卡车的特点是载重大");
}
}
public class Ferrari911 : car
{
public void Weight()
{
Console.WriteLine("法拉利911车重1.6吨");
}
public void Speed()
{
Console.WriteLine("法拉利911的特点是跑得快");
}
}
public class AudiQ7 : car
{
public void Weight()
{
Console.WriteLine("奥迪Q7车重2.2吨");
}
public void Room()
{
Console.WriteLine("奥迪Q7的特点是空间大");
}
}
class VolkswagenCC:car
{
public void Weight()
{
Console.WriteLine("大众CC车重1.6吨");
}
public void Price()
{
Console.WriteLine("大众CC的特点是性价比高");
}
}
//执行不同车型的Weight方法
class carinformation
{
public void show(car cartype)
{
cartype.Weight();
}
}
private void button1_Click(object sender, EventArgs e)
{
carinformation carweight = new carinformation();
carweight.show(new truck());
carweight.show(new Ferrari911());
carweight.show(new AudiQ7());
carweight.show(new VolkswagenCC());
new truck().CarryingCapacity();
new Ferrari911().Speed();
new AudiQ7().Room();
new VolkswagenCC().Price();
}
还是上面的车的例子,创建一个car接口,里面声明了一个Weight()方法,下面有四个类,分别代表四种车型,都使用了car接口,那么每种车型的Weight()方法都是共有的,我们在每种车型的类里都实现这个方法,输出每种车型的车重,每种车型都有别的车型没有的优势,我们给每种车型增加一个特有的方法,分别是CarryingCapacity()、Speed()、Room()、Price(),然后我们在界面上拖入一个按钮,在按钮的关联函数里输出这些信息。
最后输出结果:
小结:1.接口中的方法在使用接口的类中必须实现
2.接口中的方法声明时不能使用public、private等修饰符(所有成员默认为public)
3.接口中不能声明抽象成员(不能直接new实例化)
4.接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员
5.接口不能被实例化
6.一个类可以直接实现多个接口,接口之间用逗号隔开
7.一个接口可以有多个父接口,实现该接口的类必须实现所有父接口中的所有成员
C++实现
C++并没有像JAVA或者C#一样带有interface关键字,所以为了实现相同的功能,我们使用virtual关键字,当函数被virtual关键字修饰时,该函数被称为虚函数,下面是MSDN对虚函数的介绍:
虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
官方的定义语句比较难懂,大概的意思就是动态绑定。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。不使用多态即静态绑定时,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。
友元函数、构造函数、static静态函数不能用virtual关键字修饰;
普通成员函数和析构函数可以用virtual关键字修饰。
在一个使用virtual关键词修饰的函数后加=0后表示这是纯虚函数,表示不能在虚类里写出具体方法,如果没有加=0,就要在虚类里加具体方法。在当作接口使用时,这一步十分关键。
还是用的上面的题材实现相同的内容,下面是代码:
InterfaceExample.h
#pragma once
#include <string>
class car
{
public:
virtual void Weight()=0;
};
class truck : car
{
public:
void Weight();
void CarryingCapacity();
};
class Ferrari911 : car
{
public:
void Weight();
void Speed();
};
class AudiQ7 : car
{
public:
void Weight();
void Room();
};
class VolkswagenCC : car
{
public:
void Weight();
void Price();
};
InterfaceExample.cpp
#include "stdafx.h"
#include <iostream>
#include "InterfaceExample.h"
using namespace std;
void truck::Weight()
{
cout << "卡车车重10吨" << endl;
}
void truck::CarryingCapacity()
{
cout << "卡车的特点是载重大" << endl;
}
void Ferrari911::Weight()
{
cout << "法拉利911车重1.6吨" << endl;
}
void Ferrari911::Speed()
{
cout << "法拉利911的特点是跑得快" << endl;
}
void AudiQ7::Weight()
{
cout << "奥迪Q7车重2.2吨" << endl;
}
void AudiQ7::Room()
{
cout << "奥迪Q7的特点是空间大" << endl;
}
void VolkswagenCC::Weight()
{
cout << "大众CC车重1.6吨" << endl;
}
void VolkswagenCC::Price()
{
cout << "大众CC的特点是性价比高" << endl;
}
int main()
{
truck tru = truck();
Ferrari911 fer = Ferrari911();
AudiQ7 Aud = AudiQ7();
VolkswagenCC vol = VolkswagenCC();
tru.Weight();
fer.Weight();
Aud.Weight();
vol.Weight();
tru.CarryingCapacity();
fer.Speed();
Aud.Room();
vol.Price();
cin.get();
return 0;
}
代码十分简单,就不解析了。
最后结果相同
这边额外提一个保留字override,如果我在重载虚函数的时候将虚函数的名字拼写错了,那么这个时候编译是可以通过的,但是我们并没有实现想要的功能。
现在在重写虚函数的时候加上override,例如:
class Ferrari911 : car
{
public:
void Weight() override;
void Speed();
};
结果和前面并不会有什么不同,但是我如果将Weight这个写成了weight,那么这个时候编译是无法通过的。
override保留字的作用:
1.在函数比较多的情况下可以提示读者某个函数重写了基类虚函数(表示这个虚函数是从基类继承,不是派生类自己定义的);
2.强制编译器检查某个函数是否重写基类虚函数,如果没有则报错。
提到了override保留字就不得不提一下他的兄弟final,使用 final 保留字可以指定无法在派生类中重写的虚函数①,还可以使用它指定无法继承的类②。(感觉被结扎)
情况①
struct Object{
virtual void fun() = 0;
};
struct Base : public Object {
void fun() final; // 声明为final
};
struct Derived : public Base {
void fun(); // 无法通过编译
};
情况②
class Base final
{
};
// 错误,Derive不能从Base派生。
class Derive
: public Base
{
};