一、编译时多态(静态多态)
1. 函数重载(Function Overloading)
函数重载是指在同一个作用域内,可以有多个同名函数,但它们的参数列表(参数的类型、个数或顺序)不同。编译器在编译时根据调用时提供的参数决定使用哪个函数。
示例代码:
#include
void print(int i) {
std::cout << "整数: " << i << std::endl;
}
void print(double d) {
std::cout << "浮点数: " << d << std::endl;
}
void print(const std::string& s) {
std::cout << "字符串: " << s << std::endl;
}
int main() {
print(10); // 调用print(int)
print(3.14); // 调用print(double)
print("Hello"); // 调用print(const std::string&)
return 0;
}
2. 模板(Templates)
模板允许我们编写泛型代码,支持在编译时根据具体类型实例化相应的函数或类。模板极大地提高了代码的复用性和灵活性。
示例代码:
#include
template
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&)
std::cout << "x: " << x << ", y: " << y << std::endl;
double m = 1.1, n = 2.2;
swap(m, n); // 实例化swap(double&, double&)
std::cout << "m: " << m << ", n: " << n << std::endl;
return 0;
}
二、运行时多态(动态多态)
1. 基于继承的多态(虚函数)
运行时多态通常通过继承和虚函数来实现。基类定义虚函数,而派生类重写这些虚函数。在运行时,根据实际对象的类型调用相应的重写函数。
示例代码:
#include
class Animal {
public:
virtual ~Animal() {} // 虚析构函数,确保派生类对象正确析构
virtual void makeSound() const = 0; // 纯虚函数,让Animal成为抽象类
};
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "汪汪汪" << std::endl;
}
};
class Cat : public Animal {
public:
void makeSound() const override {
std::cout << "喵喵喵" << std::endl;
}
};
int main() {
Animal* animals[] = { new Dog(), new Cat() };
for (Animal* animal : animals) {
animal->makeSound(); // 根据实际对象类型调用Dog::makeSound或Cat::makeSound
}
// 释放内存
for (Animal* animal : animals) {
delete animal;
}
return 0;
}
2. 基于函数指针的多态
在某些情况下,我们可能不希望使用继承和虚函数来实现多态,而是希望通过函数指针来实现。这种方式在某些性能敏感的场景下可能更高效,因为它避免了虚函数表的开销。
示例代码:
#include
#include
#include
// 定义一个函数类型
using MakeSoundFunc = std::function;
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 animals = { dog, cat };
for (const auto& animal : animals) {
animal.makeSound(); // 通过函数指针调用相应的声音
}
return 0;
}
3. 基于CRTP(Curiously Recurring Template Pattern)的多态
CRTP是一种模板设计模式,它通过静态多态实现类似动态多态的行为,同时避免了虚函数表的开销。CRTP利用模板和继承,使基类能够调用派生类的实现。
示例代码:
#include
// 基类模板
template
class Animal {
public:
void makeSound() const {
// 强制转换为派生类,调用派生类的实现
static_cast(this)->makeSoundImpl();
}
};
// 派生类
class Dog : public Animal {
public:
void makeSoundImpl() const {
std::cout << "汪汪汪" << std::endl;
}
};
class Cat : public Animal {
public:
void makeSoundImpl() const {
std::cout << "喵喵喵" << std::endl;
}
};
int main() {
Dog dog;
Cat cat;
Animal& animalDog = dog;
Animal& animalCat = cat;
animalDog.makeSound(); // 调用Dog::makeSoundImpl
animalCat.makeSound(); // 调用Cat::makeSoundImpl
return 0;
}
三、总结
在C++中,多态性可以通过多种不同的形式实现,每种形式都有其独特的适用场景和优势:
- 编译时多态(函数重载和模板)提供了高度的灵活性和类型安全,且没有运行时开销,但它们在需要动态类型判断的场景中力不从心。
- 运行时多态(基于继承的虚函数、函数指针)允许程序在运行时根据对象类型做出决策,非常适合需要灵活扩展和动态行为的系统,但可能带来一定的运行时开销。
- CRTP结合了模板和静态多态,提供了类似动态多态的行为,同时避免了虚函数表的开销,适用于性能敏感且需要静态类型检查的场景。