系列汇总讲解,请移步:
C++语法|虚函数与多态详细讲解系列(包含多重继承内容)
多态分为了两种,一种是静态的多态,一种是动态的多态。
静态(编译时期)的多态
函数重载
bool compare(int, int) {}
bool compare(double, double) {}
函数名字都是一个名字,但是参数个数都不相同,最终我们代码里面调用的是哪个函数,编译器是知道的:
函数源码和汇编如下
compare(10, 20); //call compare_int_int
compare(10.5, 20.5); //call compare_double_double
从汇编代码可以看出,到底调用哪个函数,编译时就已经确定了。
模版(函数模版和类模版)
函数模版?模版函数?深入理解函数与模板与分文件编写,我在这篇文章中的第一部分CPU眼里的模版明确写出了底层的汇编代码。具体可以去看文章。
template<typename T>
bool compare(T a, T b){}
compare<int>(10, 20); //call compare<int>(int, int)
compare<double>(10.2, 20.2); //call compare<double>(double, double)
动态(运行时期)的多态
这里当然是我们回答的重点。
动态的多态指的是在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数),基类指针指向哪个派生类对象,就会调用哪个派生类对象的覆盖方法,称为多态!
怎么做到的呢?就是通过动态绑定来实现的。
指针指向的是什么类型的派生类对象,那就访问谁的虚函数指针vfptr,进而就访问谁的虚函数表vftable,最后从虚函数表拿出来的,当然就是对应的派生类对象的方法了。
面试回答以上内容基本就差不多了,下面我们一起来做一点儿实验:
多态实践
问题引入
我们提供以下类:
关系图如下:
class Animal {
public:
Animal(string name) : _name(name) {}
virtual void bark() {}
protected:
string _name;
};
//一下是动物实体类
class Cat : public Animal {
public:
Cat(string name) : Animal(name) {}
virtual void bark() { cout << _name << " bark: miao miao !" << endl; }
};
class Dog : public Animal {
public:
Dog(string name) : Animal(name) {}
void bark() { cout << _name << " bark: wang wang !" << endl; }
};
class Pig : public Animal {
public:
Pig(string name) : Animal(name) {}
void bark() { cout << _name << " bark: heng heng !" << endl; }
};
提供一个全局方法,同时测试函数如下:
void bark(Cat &cat) { cat.bark(); }
void bark(Dog &dog) { dog.bark(); }
void bark(Pig &pig) { pig.bark(); }
int main () {
Cat cat("猫咪");
Dog dog("二哈");
Pig pig("佩奇");
bark(cat);
bark(dog);
bark(pig);
}
打印的话大家肯定也能想象出来,但是里面有一个问题:
- 如果我们想继承Animal继承更多的新动物呢?
- 由于需求变更,我们可能删除现有的动物?
那么这组API的设计是不是不太合理呢?
//这里的bark API接口无法做到我们软件设计的“开-闭”原则
//即对修改关闭,对扩展开放
void bark(Cat &cat) { cat.bark(); }
void bark(Dog &dog) { dog.bark(); }
void bark(Pig &pig) { pig.bark(); }
如果我们添了一个动物,我们就需要添加一个bark函数;如果删除一个动物,就需要删除一个bark函数,太不合理了!
这样的设计不能满足高内聚,低耦合的要求。
问题解决
还记得本系列中不断提及的,指针或引用的动态绑定是多态性的基础吗?我们现在这样写代码:
void bark (Animal *p) { p->bark(); } //Animal::bark是一个虚函数,所以是动态绑定
void bark (Animal &p) { p.bark(); }
int main () {
Cat cat("猫咪");
Dog dog("二哈");
Pig pig("佩奇");
bark(&cat);
bark(&dog);
bark(&pig);
bark(cat);
bark(dog);
bark(pig);
}
使用指针或者引用,都能够完美解决问题。