在C++中,多态是一种面向对象编程的特性,允许以统一的方式处理不同类型的对象,并根据实际对象的类型来执行相应的操作。
实现原理:
多态的基本原理涉及两个概念:虚函数(virtual function)和动态绑定(dynamic binding)。
- 虚函数:用virtual修饰的函数,在基类中声明一个虚函数,在派生类中可以重新定义该函数。通过将函数声明为虚函数,可以实现运行时多态。示例如下:
class Base {
public:
virtual void foo() {
cout << "Base::foo()" << endl;
}
//添加virtual即为虚函数,类会创建一个虚函数表,并指向该函数
};
class Derived : public Base {
public:
void foo() override { // 使用override关键字显式标记重写基类虚函数
cout << "Derived::foo()" << endl;
}
//这样调用foo()函数时,会根据实际创建的类型运行相应的代码。
};
动态绑定:当使用指向派生类对象的基类指针或引用调用虚函数时,编译器会根据指针或引用所指向的具体对象类型,在运行时动态地决定要调用哪个版本的函数。示例如下:
Base* basePtr = new Derived(); // 使用基类指针指向派生类对象
basePtr->foo(); // 动态绑定,在运行时调用Derived::foo()
上述代码会输出"Derived::foo()",即程序在运行时根据实际对象类型选择了正确的虚函数版本。
通过使用虚函数和动态绑定,可以实现多态性,即在运行时根据对象的类型来调用相应的函数版本。这为代码的灵活性、可扩展性和可维护性提供了很大的好处。
虚函数定义:
多态是由虚函数实现的,而虚函数主要是通过虚函数表实现的。如果一个类中包含虚函数,那么这个类就会包含一张虚函数表,虚函数表中存储的每一项是一个虚函数的地址。该类的每个对象都会包含一个虚指针,需指针指向虚函数表。注意:对象不包含虚函数表,只有需指针,类才包含虚函数表,派生类会生成一个兼容基类的虚函数表。
多态代码演示:
#include <iostream>
using std::cout;
using std::endl;
using std::max;
using std::min;
class a {
public: // 确保虚函数是公开的
virtual int num(int n1, int n2) {
cout << "use num in a" << endl;
return max(n1, n2);
}
};
class b : public a { // 使用冒号表示继承
public: // 如果在类b中也要调用num函数,则num也需要是public的
int num(int n1, int n2) override { // override放在函数声明的末尾
cout << "use num in b" << endl;
return min(n1, n2);
}
};
int main() {
a ff1;
ff1.num(3, 5); // 输出:use num in a
b ff2;
ff2.num(3, 5); // 输出:use num in b
return 0;
}
代码运行结果:
可以看到a类b类中都有num函数。多态使用方法为:先在基类中声明共有的虚函数,派生类继承基类,派生类重写基类中虚函数。在main函数中,虚函数就会根据实际对象的不同调用相应的代码。
多态调用与普通调用区别:
多态调用:运行时决议-- 运行时确定调用函数的地址(不管对象类型,查对应的虚函数表,如果是父类的对象,就查看父类对象中存的虚表;如果是子类切片后的对象,就查看子类切片后对象中存的虚表)
普通调用:编译时决议-- 编译时确定调用函数的地址(只看对象类型去确定调用哪个对象中的函数)
虚函数使用规则:
(1)虚函数在类中声明和类外定义的时候,virtual关键字只在声明时加上,而不能加在在类外实现上,且虚函数必须为共有函数,这是由于多态作用限制。
(2)静态成员不可以是虚函数。因为静态成员函数没有this指针,使用类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
(3)友元函数不属于成员函数,不能成为虚函数
(4)静态成员函数就不能设置为虚函数。
(5)析构函数建议设置成虚函数,因为有时可能利用多态方式通过基类指针调用子类析构函数(尤其是父类的析构函数强力建议设置为虚函数,这样动态释放父类指针所指的子类对象时,能够达到析构的多态)