什么是多态

所谓的多态是通过一个单一的标识符支持不同的特定行为的能力。

多态的分类

从绑定时间

  1. 静态多态 (编译期多态)
  2. 动态多态 (运行期多态)

从表现的形式

  1. 虚函数
  2. 重载
  3. 模板
  4. 转换 (类型别名)

今天我们就只讨论从绑定时间来分的多态种类,即编译期多态和运行期多态。

运行期多态

运行期多态可以说只要学了c++的人都是知道的。因为运行期多态就是我通俗所说的多态,它的提出可以归结于类继承的思想的提出。对于相关功能的对象的集合,我们希望抽象出它们功能集合,在基类中声明为虚函数,然后再子类中去重写这些虚函数,以实现子类其特有的功能。

  • 代码示例
#include<iostream>
using namespace std;
class Animal {
public:
    //纯虚函数
    virtual void call()=0;
};
class Dog :public Animal {
public:
    void call() {//实现Dog类自己的call函数
        cout << "Dog" << endl;
    }
};

class Cat :public Animal {
public:
    void call() {//实现Cat类自己的call函数
        cout << "cat" << endl;
    }
};

int main()
{
    Animal * dog = new Dog();
    dog->call();
    Animal * cat = new Cat();
    cat->call();
    return 0;
}

运行程序输出:

Dog
cat

所以,从上面这个程序我们也看出了多态的在运行期的实现方式。而其大体的原理就是在运行过程中,我们可以确定每个Animal对象内存中存放虚函数表的内容,从而可以调用正确的函数。而具体实现可以去查找虚函数的实现原理。

编译期多态

编译期多态的主要是有两种:函数重载和模板。下面我们一一来介绍一番。

函数重载的多态

所谓的函数重载,就是具有相同的函数名但是有不同参数列表(参数列表包括参数的类型、参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同)的函数,我们叫他们为函数重载。函数重载可以在编译时候就确定我们应该调用哪个函数,所谓我们称之为编译期的多态。


不过函数重载有一个美中不足之处就是不能为返回值不同的函数进行重载。那是因为人们常常不为函数调用指出返回值。并不是技术上不能通过返回值来进行重载。

模板的多态

模板是c++泛型编程的一大利器,真是因为有它的存在,我们才可以很好的编写出大量的泛型程序。例如容器等。下面我们给出模板的多态的例子:

#include<iostream>
using namespace std;
class Dog{
public:
    void call() {
        cout << "Dog" << endl;
    }
};
class Cat{
public:
    void call() {
        cout << "cat" << endl;
    }
};
template <typename T>
void call(T & t) {
    t.call();
}
int main()
{
    Dog dog;
    Cat cat;
    call(dog);
    call(cat);
    system("pause");
    return 0;
}

运行程序输出:

Dog
cat

从上述的程序,我们定义了模板函数在编译期就已经确定了T的数据类型,所以我们就可以正确的调用恰当的函数。

总结

这篇博客,只是简单了介绍了编译期多态和运行期多态的知识,而我们除了需要知道这些只是外,我们还需要明白这两种实现机制所带来的效率问题。具体的探讨可以参考如下博客:
- C++的动态多态和静态多态
- C++编译期多态与运行期多态

最后,我再强调两个知识点:
- 函数重载:返回值和函数名相同。参数类型和参数个数可以不同
- 函数重写:是在子类中对父类函数进行重新定义的过程。但是需要注意下面几个要点:
1. 被重写的函数不能是static的。必须是virtual的,或者是override的(即函数在最原始的基类中被声明为virtual,c++中没有override)。
2. 重写函数必须有相同的类型,名称和参数列表
3. 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的(这点与C#完全不同)