前言

学习面向对象编程,关键就是要理解封装、继承、多态这三种思想,在C++中,基类与派生类之间的多态性可以由virtual关键字来实现

多态(polymorphism)

什么是多态?通俗地讲,多态就是“龙生九子,各有不同”。在当前语境下可以理解为:面对不同的实例,所执行的方法是不同的。

一个类作为基类,能够“生”出许多派生类。当我们传入某一个派生类的实例时,程序能够正确地执行与该派生类对应的方法。这就是所谓“运行时多态”

虚函数

定义

父类的某些成员函数,能在子类中被覆盖重写(override),在父类中声明时用virtual修饰,在子类中可重定义,并且可以在函数名后面标注override来提示。

如下实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>

class Ball //球类游戏
{
public:
virtual void getBallName()
{
std::cout<< "Ball"<< std::endl;
}//用的是什么球?
};


class Basketball: public Ball
{
public:
void getBallName() override
{
std::cout<<"Basketball"<<std::endl;
}//用的是什么球?
};


class Football: public Ball
{
public:
void getBallName() override
{
std::cout<<"Football"<<std::endl;
}//用的是什么球?
};

我们为Ball基类派生了篮球、足球两个子类。从Ball类继承而来的getBallName方法并不能满足子类的需求,于是在两个子类中分别各自实现一次该方法。这就是虚函数的一般用法。

如果我们都用Ball类指针指向三个不同类的实例(父类指针指向子类对象)并调用该方法,程序也能够正确识别对象类型并正确链接到对应的定义去。

1
2
3
4
5
6
7
8

Ball* ptr1 = Ball ball;
Ball* ptr2 = Basketball basket;
Ball* ptr3 = Football foot;

ptr1->getBallName();
ptr2->getBallName();
ptr3->getBallName();
1
2
3
Ball
Basketball
Football

注意

  1. 子类中重写的函数,本质上还是一个虚函数,能够被继续继承和重写。有的程序员选择在子类中也在函数前标注 virtual 以作提示。

  2. 事实上在重写函数时override的标注是非必需的,但是处于可读性还是写上吧:)

  3. 无论是virtual还是override,都只能在函数声明处使用一次,如果具体实现在其他源文件中,则源文件出不需也不能标注virtual —来自期中考的惨痛教训~

为什么要用虚函数?

一般来说,父类指针指向子类对象时,由于C++ 静态绑定(Static Binding) 的特性(在编译、链接期就确定一个函数名对应的代码块),具有类型Ball的指针只能调用Ball对象的成员,即使指针指向的这个Ball对象“实际上”是一个子类对象,甚至子类中有对应的同名成员函数重载

为了让程序学会自己找到,需要使用虚函数,让程序在运行时通过 “运行时类型信息(RTTI, Runtime Type Information)” 机制,检索匹配调用函数的对象是什么类型,对应执行的是哪一个重载。实现所谓“动态绑定(Dynamic Binding)”。

纯虚函数

很多时候父类的虚函数甚至不需要具体实现,只提供一个声明(名字),具体实现交由各个派生类负责。可以想象成中央权力下放给地方。这种虚函数就叫纯虚函数。通过纯虚函数我们能在c++里面实现抽象基类(Abstract Base Class,简称ABC),甚至是面向对象中的接口(Interface)

用法

“猫说猫话,狗说狗话”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Animal
{
private:
std::string name;
public:
std::string getName(){return name;}
virtual void talk() = 0;
};


class Cat: public Animal
{
public:
void talk() override
{
std::cout << getName() << ": I am a cat." << std::endl;
}
};


class Dog: public Animal
{
public:
void talk() override
{
std::cout << getName() << ": I am a dog." << std::endl;
}
};

直接写成形如virtual type foo() = 0;的形式。

这种情况下我们根本不会实例化Animal基类。Animal被称作一个抽象类,而getVoice()就是一个接口,只有实现了这个方法的子类才能实例化对象。