1、引言
多态是面向对象编程的一种特性,简单来说就是调用同名函数,根据具体的对象类型、或者参数类型,执行不同的方法实现。在C++中多态有两种实现方式,即静态多态(编译时多态)和动态多态(运行时多态)。
2、静态多态
2.1、概念
也称编译期多态。静态多态是指在编译时就能够确定函数调用的具体实现,主要包括函数重载、模板和隐藏3种实现方式。函数重载是指在同一个作用域内,名称相同的函数参数类型或参数个数不同,从而可以根据调用时提供的实参来确定实际调用的函数。模板多态是指编译时进行代码生成,根据传递的模板参数不同,生成不同的代码。隐藏是指在子类中定义父类同名同参数函数。
2.2、实现方法
C++中的静态多态可以通过函数重载、模板以及隐藏实现,以下分别展示各自的使用例子。
2.2.1、通过模板实现静态多态:
#include <iostream>
template<typename T>
T max(T a, T b){
return a > b ? a : b;
}
int main()
{
// 输出20,调用max<int>函数
std::cout << max(10, 20) << std::endl;
//输出3.14,调用max<double>函数
std::cout << max(1.2, 3.14) << std::endl;
return 0;
}
2.2.2、通过函数重载实现静态多态:
#include <iostream>
void print(int num){
std::cout << "The integer is " << num << std::endl;
}
void print(double num){
std::cout << "The double is " << num << std::endl;
}
int main(){
print(10); // 调用第一个print函数
print(3.14); // 调用第二个print函数
return 0;
}
2.2.3、通过隐藏实现静态多态——在子类中定义父类同名同参数函数。
class Rectangle{
public:
void name() {
std::cout<<"矩形"<<std::endl;
}
};
class Circle {
public:
void name() {
std::cout<<"圆形"<<std::endl;
}
};
template<typename T>
void print(T shape){
shape.name();
}
int main() {
print<Rectangle>();//矩形
print<Circle>();//圆形
return 0;
}
2.3、原理
静态多态都是通过name mangling实现的。name mangling是一种编译器技术,用于为程序中的变量和函数生成唯一的内部名称,这个过程涉及改变原始名称的形式,从而避免了变量和函数之间名称的冲突。name mangling规则的具体实现方式因编译器而不同,下面是一些常见编译器的name mangling规则:
- MSVC编译器:在函数名前加上下划线,然后加上函数名,以及加上所有参数的数据类型与长度信息,最终生成修饰过的函数名。例如,函数名foo(int, float)会被修饰为_foo@8。其中8是函数参数的总长度,指示了函数的参数类型。
- G++编译器:以函数名和参数类型组成的字符串作为函数的新名字。例如,函数名foo(int, float)会被编译器重命名为_Z3fooif,其中Z表示这是一个C++函数,3表示该函数名长度为3,f和i分别表示两个参数的数据类型为float和int。
- Clang编译器:类似于GCC,使用函数名和参数类型生成一个字符串作为新的函数名,不过字符串的格式可能略有不同。
编译前,不同名称空间内的同名函数、同名但参数不同的函数,经过name mangling处理,都有唯一名称,从而能够被区分。
2.4、CRTP
CRTP是Curiously Recurring Template Pattern(奇怪的重复模板模式)的缩写,是一种利用继承和模板技术实现的编程模式,用于在编译时实现静态多态,也称为根据类型递归静态多态,它是静态多态使用案例中的一种最佳实践。CRTP通过继承一个模板类,并在模板参数中传递自身类型,来实现静态多态,举个例子:
template<typename T>
class Base {
public:
void foo() { static_cast<T *>(this)->impl_foo(); }
};
class Derived1 : public Base<Derived1> {
public:
void impl_foo() { cout << "Derived1 foo" << endl; }
};
class Derived2 : public Base<Derived2> {
public:
void impl_foo() { cout << "Derived2 foo" << endl; }
};
3、动态多态
3.1、概念
也称运行时多态。C++中的动态多态是通过虚函数(virtual function)实现的。其基本使用是:在父类中定义一个虚函数,在子类中重写这个虚函数。在程序运行时,通过基类指针或引用指向子类对象,并使用基类对象调用虚函数时,会自动调用子类的重写版本。
3.2、使用方法
以下是一个使用动态多态的例子。假设我们需要实现一个图形类Shape,包含一个虚函数 say() 用于表面该图形的形状。我们要实现两个派生类 Circle 和 Rectangle,分别用于表示圆形和矩形,它们重写了基类 Shape 中的虚函数 say()。
class Shape {
public:
virtual void say() = 0;
};
class Rectangle: public Shape {
public:
void say() {
std::cout<<"矩形"<<std::endl;
}
};
class Circle: public Shape {
public:
void say() {
std::cout<<"圆形"<<std::endl;
}
};
接下来就可以通过基类指针调用同名函数的不同实现了。
int main()
{
Shape *shape;
Rectangle rec;
Circle cir;
shape = &rec;
shape->say();//矩形
shape = ○
shape->say();//圆形
return 0;
}
3.3、原理
C++动态多态是通过虚函数和虚函数表实现的。C++编译器为每一个有虚函数的类生成一个虚函数表,虚函数表是一个指向各个虚函数的函数指针数组,类实例化成对象后,对象首地址处存放有一个指针vptr指向虚函数表。
类继承的情况下,每个子类以及当前类都会有一个虚函数表。每个子类的虚函数表,会存储子类和父类所有的虚函数地址,如果子类与父类有同名虚函数,则只填子类中的同名虚函数地址。
当程序运行时,基类指针或引用调用虚函数,会根据对象的实际类型(即运行时类型),选择一个虚函数表,并在虚函数表中查找对应的函数指针,然后调用指向的具体实现代码。
4、总结
总之,多态是面向对象编程的一个重要特性,通过多态可以增强代码的灵活性和可扩展性、提高代码的可复用性、简化代码的设计和维护以及改善代码的可读性和可理解性,包括静态多态和动态多态,两者互有优劣,适用于不同场景。