背景
今天在学习一个代码案例的时候,首先声明了一个类,这个类继承了了一个父类,其中一个方法前面带了visual
关键字,且最后面还写上了= 0
,如下
virtual void xxxx(const xxx ¶m1, xxx ¶m2)=0;
刚学这个并不知道是什么意思,实现的时候直接全选复制到子类的头文件了。结果就报错:
错误 30 error C2259: “CRTBapiDemo”: 不能实例化抽象类
然后我双击错误信息点击去看了一下错误原因:
46 IntelliSense: 不允许使用抽象类类型 "CRTBapiDemo" 的对象:
函数 "CRTBapiDemo::callback" 是纯虚拟函数
出身Java的我看到抽象类型无法实例化对象,就敏感起来了。于是我查了一下C++中的virtual,原来与Java中的abstract和interface有一定的类似,所以我想弄清楚这几者之间的关系。
Virtual是干什么的
虚函数是指一个类中你希望重载的成员函数 ,当你用一个基类指针或引用
指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本。这是C++多态特性的一种体现形式。
观察以下代码:
#include<iostream>
using namespace std;
//基类
class ClassA
{
public:
virtual void foo() { cout << "A::foo() is called" << endl; }
};
// 派生类(继承类)
class ClassB : public ClassA
{
public:
virtual void foo() { cout << "B::foo() is called" << endl; }
};
int main()
{
ClassA *ptr = new ClassB();
ptr->foo();
getchar();
return 0;
}
结果是
B::foo() is called
由此可见,虽然ptr是ClassA的指针,但是执行foo()函数的时候,打印的却是ClassB的foo()。
virtual关键字的性质
c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。
例如:
class A
{
public:
virtual void foo();
};
class B: public A
{
public:
void foo(); // 没有virtual关键字!
};
class C: public B // 从B继承,不是从A继承!
{
public:
void foo(); // 也没有virtual关键字!
};
这种情况下,B::foo()是虚函数,C::foo()也同样是虚函数。
纯虚函数
菜鸟教程中是这样解释的:您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
换句话说,我们只是抽象了个函数模板,函数实现需要由不同的派生类去结合自己情况,具体实现
。
由此可以拓展出以下两点:
- 含有纯虚函数的类被称为抽象类
对于抽象的类来说,我们往往不希望它能实例化,因为实例化之后也没什么用,而对于一些具体的类来说,我们要求必须实现那些要求(纯虚函数),使之成为有具体动作的类- 仅含有纯虚函数的类称为接口类
1、没有任何数据成员
2、仅有成员函数
3、成员函数都是纯虚函数
因此,一个函数声明为纯虚后,它的意思是:“我是一个抽象类!不要把我实例化!”纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。
下面来测试一下,如果子类不去实现基类的纯虚函数,会发生什么:
class ClassA
{
public:
virtual void foo() = 0;
};
// 派生类
class ClassB : public ClassA{};
int main()
{
ClassA *ptr = new ClassB();//这里会报错
ptr->foo();
getchar();
return 0;
}
编译器会直接在new ClassB()
这里飘红:
Java中的abstract
这里默认看到文章的人都像我一样,是在熟悉Java的基础上学的C++~
1、用abstract关键字来表达的类,其表达形式为:(public)abstract class 类名
2、抽象类不能被实例化,也就是说我们没法直接new 一个抽象类。抽象类本身就代表了一个类型,无法确定为一个具体的对象,所以不能实例化就合乎情理了,只能有它的继承类实例化。
3、抽象类虽然不能被实例化,但有自己的构造方法(这个后面再讨论)
4、抽象类与接口(interface)有很大的不同之处,接口中不能有实例方法去实现业务逻辑,而抽象类中可以有实例方法,并实现业务逻辑,比如我们可以在抽象类中创建和销毁一个线程池。
5、抽象类不能使用final关键字修饰,因为final修饰的类是无法被继承,而对于抽象类来说就是需要通过继承去实现抽象方法,这又会产生矛盾。
Java中,默认所有的函数都是虚函数,所以Java的多态比C++用起来更简洁,但是针对C++中的纯虚函数,Java中怎么体现呢?
interface类
一开始用注解的同学一定有过这样的疑问:
@Autowire
DemoService demoService;
“我注入了一个interface类,就可以直接用接口里的方法?!但是interface里没有方法体啊!”其实这是@Autowire
注解的作用,它帮你找到该interface的实现类,注入进来的。这里咱们先不考虑注解,一个小疑问,就能看出interface的特点:
- 只有方法名,没有方法体
- 没有成员属性及构造方法
- 需要自己写类来implement该接口类,且必须重写所有interface的方法
看到这里是不是感觉virtual xxx() = 0和interface类特别相似!你无法使用interface去实例化,并且你要继承我,你必须实现我定义的方法。
关系
所以C++中的virtual修饰方法以及virtual xxx() = 0,与Java里的abstract和interface的关系就清晰了。
- 纯虚函数定义后必须被子类实现,类似interface,整个类称为抽象类
- 没有声明纯虚函数的C++类不叫抽象类,即使有virtual修饰的虚函数,也可以实例化对象,而Java中的abstract抽象类和interface接口类都不可以实例化对象。
- 普通虚函数,子类可以实现,也可以不实现。
其实声明了纯虚函数的C++类,就是Java中abstract和interface的结合体,不但和abstract一样可以有成员属性,而且子类必须重写纯虚函数。