本文为 C/C++ 的学习总结,讲解多态。欢迎在评论区与我交流👏
多态的基本概念
多态分为两类
- 静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
下面通过案例讲解多态。首先我们定义动物类,并派生出猫类:
class Animal{
public:
void speak(){
cout << "动物在说话" << endl;
}
};
class Cat:public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
再写一个执行说话的函数并进行测试,用父类 Animal
的引用指向子类 Cat
的对象,相当于 Animal & animal = cat;
,C++ 中允许父子之间的类型转换,无需强制类型转换:
void doSpeak(Animal& animal) {
animal.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
}
如果我们运行程序,会发现运行结果为“动物在说话”,然而我们本意是想让猫说话。我们这种写法为地址早绑定,在编译阶段就确定了函数地址,调用 speak()
函数时在编译时就确定了是执行 animal
的 speak()
函数。如果我们想让猫说话,就要让地址晚绑定,需要在运行阶段进行绑定,只需要在函数前加关键字 virtual
,变为虚函数。
virtual void speak(){
cout << "动物在说话" << endl;
}
这时的运行结果为“小猫在说话”。此时我们再从 Animal
类中派生出 Dog
子类,同样传入 Dog
的对象:
class Dog :public Animal {
public:
void speak() {
cout << "小狗在说话" << endl;
}
};
打印结果为:
这样一来,函数在运行时才能确定调用谁的 speak()
函数,如果传入 cat
就走 cat
的 speak()
函数。
以上就实现了动态多态。总结一下,动态多态的满足条件为:
- 有继承关系
- 子类要重写父类中的虚函数。重写是返回值、函数名、参数列表完全相同。子类加不加
virtual
都可以。
使用动态多态,我们需要让父类的指针或引用指向子类对象。即 Animal & animal = cat;
。
纯虚函数和抽象类
通常在父类中写的虚函数是用不上的,例如上面的 Animal
类中的 speak()
函数,我们通常会调用子类重写的函数。这时我们可以使用纯虚函数,只要类中有一个纯虚函数,我们将这个类称为抽象类。
抽象类有两个特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
例如我们构建一个 Base
类,将其中的函数写为纯虚函数的形式:
class Base {
public:
// 纯虚函数
virtual void func() = 0;
};
我们分别在栈上和堆上实例化一个对象:
void test01() {
Base b;
new Base;
}
此时发生报错,抽象类是无法实例化对象的。
我们再派生出一个 Son
类,不重写父类的纯虚函数,并实例化一个对象:
class Son :public Base {
public:
};
void test01() {
//Base b;
//new Base;
Son s;
}
此时同样会报错。我们在子类 Son
中重写父类的虚函数,并使用父类指针指向子类对象:
class Son :public Base {
public:
virtual void func(){
cout << "func函数调用" << endl;
}
};
void test01() {
//Base b;
//new Base;
//Son s;
Base* base = new Son;
base->func();
}
运行结果为“func函数调用”。
有帮助的话点个赞加关注吧 😃