C++高层抽象之虚函数

C++中的多态(Polymorphism)有两种,编译时多态和运行时多态。编译时多态包括函数重载(overload)和模板(template)等,而运行时多态主要是虚函数的重写(override)。

首先区分几个概念:
重载(overload): 相同作用范围(或命名空间)的同名函数的不同实现,只能通过参数列表来区分。
重写(override): 在子类中重新实现父类中定义的虚函数,以支持动态绑定(dynamic binding)。
覆盖(overwrite):在子类中定义中与父类中函数同名的函数,若不构成重写,则父类中的同名函数被隐藏。类似于命名空间的覆盖。

虚函数的声明

在成员函数的声明前加上 virtual 关键字就成了虚函数。在类外部的函数实现前不要加上 virtual。静态成员函数(static)不能被声明为虚函数。

virtual function_declaration ;

一旦类A的某个函数被声明为虚函数,所有类A的派生类都将包含这个虚函数,且可以对其进行重写。重写时,可以省去 virtual 关键字(笔者通常会明确写出virtual)。

一个特殊情况是构造函数,构造函数不能是虚函数。同时也不能在构造函数中调用虚函数,因为对象构造好之前它的调用表是不完整的,此时调用的函数是不确定的。

c++11引入了两个新关键字 overridefinal
override 明确声明一个函数是重写的,如果这个函数不能被重写,编译器将会报错。

virtual void scan() override;

final 明确声明一个函数是不可被重写的,如果在派生类中对其进行重写,编译器将会报错。

virtual void scan() final;

overridefinal 可以一起使用,没有先后顺序。

virtual void scan() override final;

虚函数的使用

虚函数最通常都是用来实现运行时多态,比如

class A {
public:
    virtual void fun();
};
class B: public A {
public:
    virtual void fun() override;
};
int main() {
    A *p=new B;
    A->fun();
    return 0;
}

此时调用的是实际类B的fun()函数,而不是定义类A的fun()函数。

纯虚函数

纯虚函数(pure virtual)是一种虚函数,它使得类变成抽象类(abstract class),抽象类不能被实例化。通过重载可以使纯虚函数变为普通虚函数,类变成普通类。也可以将纯虚函数继续重载为纯虚函数。

    virtual void fun()=0;
    virtual void fun() override;
    virtual void fun() override=0;
    virtual void fun() final;
    virtual void fun() final=0;

注意第5种声明方式,它将fun()声明为一个纯虚函数,且不能被重载。这个类将永远不能被实例化。

纯虚函数声明时不能进行函数体定义,只能将定义放在类外,这是由“初始化式的”语法所限制的。某些编译器扩展支持直接定义。
纯虚函数可以没有函数体定义,它的功能将由子类重写的函数实现。如果调用没有函数体定义的纯虚函数,将会发生链接错误。

class A {
public:
    int a;
    virtual void fun()=0;
};
class B: public A {
public:
    virtual void fun() override {}
};
/*
void A::fun() {
    printf("A::fun");
}
*/
int main()
{
    A *p=new B;
    p->A::fun();
    return 0;
}

A.cpp:(.text+0x2a): undefined reference to `A::fun()’

虚析构函数与纯虚析构函数

如果类A的析构函数是虚函数,则所有类A的派生类的析构函数(包括默认析构函数)都是虚函数,且可以被重写,下面的代码将调用类B的析构函数。

class A {
public:
    virtual ~A();
};
class B: public A {
public:
    virtual ~B() override;
};
int main() {
    A *p=new B;
    delete p;
    return 0;
}

纯虚析构函数比较特殊,纯虚析构函数通常不能省略函数体定义,因为子类的析构函数会隐式调用父类的析构函数。

class A {
public:
    int a;
    virtual ~A()=0;
};
class B: public A {};
int main()
{
    A *p=new B;
    delete p;
    return 0;
}

A.cpp:(.text._ZN1BD2Ev[_ZN1BD5Ev]+0x20): undefined reference to `A::~A()’

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值