我们在《C++继承的理解》中讲过在实现继承关系中成员方法的关系时覆盖的条件涉及到了一个以前未听过的概念——虚函数,那么今天我们就来详细介绍一下虚函数的定义,特例和应用。
一、虚函数的定义
虚函数必须是基类的非静态成员函数,其访问权限可以是private或protected或public,在基类的类定义中定义虚函数的一般形式——在某基类中声名为virtual并在一个或多个派生类中重新定义的成员函数,可实现函数动态重载,实现多态性。
二、虚函数的作用
虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的子类中对虚函数重新定义,在子类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在子类中没有对虚函数重新定义,则他继承其基类的虚函数。
当程序发现函数名前的关键字virtual后,会将其自动作为动态汇编处理,即在程序运行时动态地选择合适的成员函数。虚函数是C++动多态的一种表现。
例如,虚函数的2个例子:
1.子类如何继承父类的成员方法
答:我们把父类的指针指向子类,再把父类的该函数(方法)设为virtual(虚函数)。
2.如何去访问基类中的虚函数呢?
答:动态汇编规定,只有通过指向基类的指针或基类对象的引用来调用虚函数,其格式:
1.指向基类的指针变量名->虚函数名(实参表)
2.基类对象的引用名.虚函数名(实参表)
使用虚函数,我们可以灵活的进行动态绑定,当然是以一定的开销为代价。如果父类的函数(方法)根本没有必要或无法实现,完全依赖子类去实现的话,可以把此函数(方法)设为virtual函数名=0,我们把这样的函数(方法)称为纯虚函数。
三、纯虚函数
这里,我们引出了虚函数的特例——纯虚函数
下面,我将举例
#include<iostream>
using namespace std;
#include<string>
class animal //动物类
{
public:
animal(string name)
:_name(name)
{
}
virtual void call() = 0; //叫声(纯虚函数)
protected:
string _name;
};
class Cat:public animal //猫类,动物的子类
{
public:
Cat(string name)
:animal(name)
{
}
void call() //喵喵喵叫(纯虚函数的实现)
{
cout << _name << " miao miao miao" << endl;
}
};
class Dog :public animal //狗类,动物的子类
{
public:
Dog(string name)
:animal(name)
{}
void call() //汪汪汪叫(纯虚函数的实现)
{
cout << _name << " wang wang wang" << endl;
}
};
int main()
{
Cat cat("HelloKity");
Dog dog("Poodle"); //贵宾犬
Cat *pa = &cat;
Dog *pb = &dog;
pa->call();
pb->call();
animal *pc = &cat;
//抽象基类中想要调用纯虚函数,可以通过实现子类中实现纯虚函数再对基类中的纯虚函数进行覆盖的方法
pc->call();
animal *pd = &dog;
//抽象基类中想要调用纯虚函数,可以通过实现子类中实现纯虚函数再对基类中的纯虚函数进行覆盖的方法
pd->call();
return 0;
}
为什么会引进纯虚函数呢?
1.引进纯虚函数是因为可能在基类中实现该函数根本毫无意义,比如说,就按我上面举的例子,假设我们在基类animal中实现了call的成员方法,有什么意义呢,我们可以将动物的叫声规定下来吗?不可能的。这就是引进纯虚函数的意义,在基类中没有意义,所以我们不写,而在下面具体的子类中实现这个函数,使其有实际意义。
2.我们经常在基类中定义虚函数来使用多态性
3.有一些类自身实例化对象是不合适的,如我上面举的例子,animal可以有子类——猫和狗,但其自身实例化出一个对象——动物,明显是不合适。而有纯虚函数的类叫做抽象类,其就被定义自身不能实例化出对象,这样就解决了这个问题。
四、抽象类
包括纯虚函数的类就是抽象类,抽象类不能实例化对象,但可以定义指针。如上面我所举的例子中,animal就是一个抽象类。