C++多态的实现方式
目录
1.引言
2.编译时多态(静态多态)
2.1.函数重载(Function Overloading)
2.2.模板(Templates)
3.运行时多态(动态多态)
3.1.基于虚函数的多态
3.2.基于函数指针的多态
3.3.基于CRTP(Curiously Recurring Template Pattern)的多态
4.总结
1.引言
在C++中,多态性是面向对象编程(OOP)的核心特性之一,它允许程序在运行时根据对象的实际类型来调用相应的方法。多态性使得代码更具灵活性和可扩展性,是设计大型复杂系统时不可或缺的工具。本文将详细探讨C++中支持的几种不同形式的多态,并通过实例代码来加深理解。
2.编译时多态(静态多态)
2.1.函数重载(Function Overloading)
函数重载允许在相同的作用域内定义多个具有相同名称但参数列表不同的函数。这些函数可以有不同的返回类型,但返回类型本身并不参与函数重载的决策过程,它只依赖于函数的名称和参数列表。参数列表的不同可以体现在参数的类型、参数的个数或者参数的顺序上。
函数重载的基本规则
1)函数名必须相同:重载函数必须具有相同的名称。
2)参数列表必须不同:参数列表的不同可以是参数的类型、数量或顺序不同。
3)返回类型可以不同:虽然返回类型不参与函数重载的决策,但重载函数可以有不同的返回类型。
4)仅通过返回类型不同的函数不构成重载:如果两个函数仅在返回类型上有所不同,那么它们不是重载函数。
5)仅通过函数参数名不同也不构成重载:函数重载的判定基于参数的类型、数量和顺序,与参数名无关。
示例代码:
#include <iostream>
using namespace std; class Box {
public: // 函数声明 void display(void); void display(int width); void display(int width, int height); // 成员函数定义 void display(void) { cout << "Displaying Box" << endl; } void display(int width) { cout << "Width of box : " << width << endl; } void display(int width, int height) { cout << "Width of box : " << width << ", Height of box : " << height << endl; }
}; int main() { Box Box1; // 调用函数 Box1.display(); Box1.display(5); Box1.display(5, 10); return 0;
}
2.2.模板(Templates)
模板允许我们编写泛型代码,支持在编译时根据具体类型实例化相应的函数或类。模板极大地提高了代码的复用性和灵活性。
示例代码:
#include <iostream>template <typename T>
void swap(T& a, T& b) {T temp = a;a = b;b = temp;
}int main() {int x = 10, y = 20;swap(x, y); // 实例化swap<int>(int&, int&)std::cout << "x: " << x << ", y: " << y << std::endl;double m = 1.1, n = 2.2;swap(m, n); // 实例化swap<double>(double&, double&)std::cout << "m: " << m << ", n: " << n << std::endl;return 0;
}
3.运行时多态(动态多态)
3.1.基于虚函数的多态
基于虚函数的多态,也称为动态多态或运行时多态,是 C++ 中最常用的一种多态形式。它允许我们通过基类指针或引用来调用派生类(子类)中重写(Override)的虚函数。
在基类中,使用 virtual
关键字声明的函数称为虚函数。虚函数允许在派生类中被重写。当通过基类指针或引用调用虚函数时,将根据指针或引用所指向的对象的实际类型来调用相应的函数版本。
示例代码:
#include <iostream>
using namespace std; // 基类
class Shape {
public: // 虚函数 virtual void draw() const { cout << "Drawing a shape" << endl; } // 虚析构函数(好习惯,但在此示例中不是必需的) virtual ~Shape() {}
}; // 派生类
class Circle : public Shape {
public: // 重写虚函数 void draw() const override { cout << "Drawing a circle" << endl; }
}; // 另一个派生类
class Rectangle : public Shape {
public: // 重写虚函数 void draw() const override { cout << "Drawing a rectangle" << endl; }
}; // 使用多态
void drawShape(const Shape& shape) { shape.draw(); // 根据shape的实际类型调用相应的draw()
} int main() { Circle circle; Rectangle rectangle; drawShape(circle); // 输出: Drawing a circle drawShape(rectangle); // 输出: Drawing a rectangle return 0;
}
3.2.基于函数指针的多态
在某些情况下,我们可能不希望使用继承和虚函数来实现多态,而是希望通过函数指针来实现。这种方式在某些性能敏感的场景下可能更高效,因为它避免了虚函数表的开销。
示例代码:
#include <iostream>
#include <functional>
#include <vector>// 定义一个函数类型
using MakeSoundFunc = std::function<void()>;class Animal {
public:Animal(MakeSoundFunc makeSound) : makeSound_(makeSound) {}void makeSound() const {makeSound_();}
private:MakeSoundFunc makeSound_;
};int main() {auto dogSound = []() { std::cout << "汪汪汪" << std::endl; };auto catSound = []() { std::cout << "喵喵喵" << std::endl; };Animal dog(dogSound);Animal cat(catSound);std::vector<Animal> animals = { dog, cat };for (const auto& animal : animals) {animal.makeSound(); // 通过函数指针调用相应的声音}return 0;
}
3.3.基于CRTP(Curiously Recurring Template Pattern)的多态
基于CRTP(Curiously Recurring Template Pattern,又称作“好奇地重复出现的模板模式”)的多态性实现,是一种在C++中利用模板和继承来提供类似于运行时多态的静态多态性的技术。CRTP通过让派生类继承一个以自身为模板参数的基类模板来实现。这种技术通常用于编译时多态,但它也可以被用来模拟或增强某些运行时多态的特性。
CRTP本身并不直接提供像虚函数那样的运行时多态性,但它可以通过模板元编程和静态多态来提供类型安全和高效的代码复用。然而,你可以结合使用CRTP和虚函数来实现一种混合的多态机制,其中CRTP用于编译时优化和类型检查,而虚函数用于需要运行时多态性的场合。
示例代码:
#include <iostream>
#include <string> // CRTP基类模板
template<typename Derived>
class Base {
public: // 一个虚函数,可以在派生类中被重写 virtual void doSomething() const { // 默认实现,使用Derived类型来调用一个静态多态函数 static_cast<const Derived*>(this)->doSomethingImpl(); } // 一个纯虚函数,强制派生类实现 virtual std::string getType() const = 0; // 一个非虚函数,利用CRTP直接在编译时确定类型 void printType() const { std::cout << "Type: " << getType() << std::endl; } protected: // 允许派生类访问这个受保护的函数来执行默认实现 void doSomethingImpl() const { std::cout << "Doing something in Base" << std::endl; }
}; // 派生类
class Derived : public Base<Derived> {
public: // 重写虚函数 std::string getType() const override { return "Derived"; } // 重写doSomethingImpl,而不是doSomething(后者在Base中通过CRTP调用) void doSomethingImpl() const override { // 注意:这里并没有直接重写doSomething,而是重写了doSomethingImpl // 因为doSomething在Base中是虚函数,但doSomethingImpl不是虚函数(也不是在Base中声明的) // 我们通过CRTP在Base的doSomething中调用它 std::cout << "Doing something in Derived" << std::endl; } // 注意:这里没有直接重写doSomething,因为它在Base中是虚函数 // 如果需要,可以直接在Derived中重写doSomething,但这将隐藏Base中的实现
}; int main() { Base<Derived>* basePtr = new Derived(); // 调用虚函数,展示运行时多态 basePtr->doSomething(); // 输出: Doing something in Derived // 调用非虚函数,但利用CRTP和虚函数getType展示类型信息 basePtr->printType(); // 输出: Type: Derived delete basePtr; return 0;
} // 注意:上面的代码示例中有一些不精确之处,特别是关于doSomethingImpl的“重写”
// 实际上,doSomethingImpl并不是Derived从Base继承的虚函数,而是Base中的一个受保护成员函数
// Derived通过CRTP继承了Base<Derived>,从而可以访问并“重写”这个受保护的成员函数
// 这种模式允许我们在编译时就知道要调用哪个版本的doSomethingImpl,同时保持doSomething的虚函数特性
4.总结
在C++中,多态性可以通过多种不同的形式实现,每种形式都有其独特的适用场景和优势:
-
编译时多态(函数重载和模板)提供了高度的灵活性和类型安全,且没有运行时开销,但它们在需要动态类型判断的场景中力不从心。
-
运行时多态(基于继承的虚函数、函数指针)允许程序在运行时根据对象类型做出决策,非常适合需要灵活扩展和动态行为的系统,但可能带来一定的运行时开销。
-
CRTP结合了模板和静态多态,提供了类似动态多态的行为,同时避免了虚函数表的开销,适用于性能敏感且需要静态类型检查的场景。