1. 简介
多态按字面的意思就是多种形态。当类与类之间存在继承关系的时候,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。多态分为为静态多态和 动态多态 两种。
平常说的多态是 动态多态
2. 静态多态
静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错 。 该种方式的出现有两处地方: 函数重载 和 泛型编程 | 函数模板
- 特点:
- 使用场景
- 函数重载(Function Overloading): 在同一个作用域内定义了多个同名但参数列表不同的函数。根据函数调用时传递的参数类型或数量,编译器会决定调用哪个具体的函数。
#include <iostream>
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
void print(double num) {
std::cout << "Double: " << num << std::endl;
}
int main() {
int a = 20;
double b = 3.14;
print(a); // 调用print(int)
print(b); // 调用print(double)
return 0;
}
- 模板(Template): 使用模板可以编写泛型代码,在编译时生成对应特定类型的代码,从而实现静态多态。
#include <iostream>
template<typename T>
void print(T value) {
std::cout << "Value: " << value << std::endl;
}
int main() {
int a = 20;
double b = 3.14;
print(a); // 根据实参类型生成print<int>(int)
print(b); // 根据实参类型生成print<double>(double)
return 0;
}
3. 动态多态
动态多态: 指只有在运行的时候才能决定到底调用哪个类的函数
动态多态的必须满足两个条件:
代码:
#include <iostream>
using namespace std;
class father{
public:
void doSomething(){
cout << "父亲在做事..." <<endl;
}
};
class son : public father{
public:
void doSomething(){
cout << "儿子在干活..." <<endl;
}
};
int main() {
//father &f0 = father f0; 报错,'father' does not refer to a value
//这是父类的对象
father f;
f.doSomething();
//父类的引用能接受父类的对象
father &f1 = f;
f1.doSomething();
//父类的指针接收父类的对象。
father * f2 =new father();
f2->doSomething();
//子类的指针接收子类的对象
son * s = new son();
s->doSomething();
//父类的指针接收子类对象
father *f4 = new son();
f4->doSomething();
// son * s1 =new father(); 报错 Cannot initialize a variable of type 'son *' with an rvalue of type 'father *'
return 0;
}
运行结果:
父亲在做事...
父亲在做事...
父亲在做事...
儿子在干活...
父亲在做事...
- 特点
- 使用场景
- 多态行为:当有多个派生类对象,但是希望以一种统一的方式处理它们时,动态多态性非常有用。通过使用基类的指针或引用,可以在运行时根据实际对象的类型来选择调用的函数,实现多态行为。
#include <iostream>
// 基类 Shape
class Shape {
public:
virtual double area() const = 0;
};
// 派生类 Rectangle
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
};
// 派生类 Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
};
int main() {
Shape* shape1 = new Rectangle(4, 3);
Shape* shape2 = new Circle(5.0);
std::cout << "Rectangle area: " << shape1->area() << std::endl;
std::cout << "Circle area: " << shape2->area() << std::endl;
delete shape1;
delete shape2;
return 0;
}
- 统一接口:通过使用基类的指针或引用,可以定义一个通用的接口,处理一组派生类对象。这样可以在不了解具体派生类的情况下,统一地访问它们的接口,提供代码的灵活性和可维护性。
#include <iostream>
// 基类 Drawable
class Drawable {
public:
virtual void draw() const = 0;
};
// 派生类 Rectangle
class Rectangle : public Drawable {
public:
void draw() const override {
std::cout << "Drawing a rectangle." << std::endl;
}
};
// 派生类 Circle
class Circle : public Drawable {
public:
void draw() const override {
std::cout << "Drawing a circle." << std::endl;
}
};
void drawShapes(const Drawable& shape) {
shape.draw();
}
int main() {
Rectangle rectangle;
Circle circle;
drawShapes(rectangle);
drawShapes(circle);
return 0;
}
- 扩展性:通过继承和虚函数,动态多态性提供了一种灵活的方式来扩展代码。当需要添加新的派生类时,只需继承基类并重写虚函数即可,而不需要修改已有的代码。
#include <iostream>
// 基类 Animal
class Animal {
public:
virtual void sound() const {
std::cout << "Animal makes sound." << std::endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
void sound() const override {
std::cout << "Dog barks." << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void sound() const override {
std::cout << "Cat meows." << std::endl;
}
};
// 扩增派生类 Bird
class Bird : public Animal {
public:
void sound() const override {
std::cout << "Bird sings." << std::endl;
}
};
int main() {
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
Animal* animal3 = new Bird();
animal1->sound();
animal2->sound();
animal3->sound();
delete animal1;
delete animal2;
delete animal3;
return 0;
}
- 注意点
4. 虚函数
C++中的虚函数的作用主要是实现了多态的机制 , 有了虚函数就可以在父类的指针或者引用指向子类的实例的前提下,然后通过父类的指针或者引用调用实际子类的成员函数。这时父类的指针或引用具备了多种形态。定义虚函数:在函数声明前,加上 virtual
关键字即可。 在父类的函数上添加 virtual 关键字,可使子类的同名函数也变成虚函数。
如果基类指针指向的是一个基类对象,则基类的虚函数被调用 ,如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用。
- 特点
4.1 工作原理
4.2 构造函数可以是虚函数吗?
构造函数不能为虚函数 , 因为虚函数的调用,需要虚函数表(指针),而该指针存在于对象开辟的空间中,而对象的空间开辟依赖构造函数的执行,这就矛盾了。
#include <iostream>
using namespace std;
class father{
public:
// virtual father(){} Constructor cannot be declared 'virtual'
father(){
cout << "父类的构造..." <<endl;
}
};
class son : public father{
public:
son(){
cout << "子类的构造..." <<endl;
}
};
int main() {
son s;
return 0;
}
4.3 析构函数可以是虚函数吗?
using namespace std;
class father{
public:
father(){
cout << "父类的构造..." <<endl;
}
virtual ~father(){
cout << "父类的析构..." <<endl;
}
};
class son : public father{
public:
son(){
cout << "子类的构造..." <<endl;
}
~son(){
cout << "子类的析构..." <<endl;
}
};
int main() {
father * f = new son ();
delete f;
return 0;
}
5. 纯虚函数
纯虚函数是一种特殊的虚函数,C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。C++中的纯虚函数更像是“只提供声明,没有实现”,是对子类的约束。
纯虚函数就是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”。
代码:
#include <iostream>
using namespace std;
class Animal{
public:
// 动物类的吃的行为,看起来更像是对子类的一种抽象,或者是看起来像是一个功能的声明。
virtual void eat() = 0 ;
/*virtual void eat(){
cout << "动物在吃..." <<endl;
}*/
};
class Bear : public Animal{
public:
void eat(){
cout << "熊吃鱼..." <<endl;
}
};
class Tiger : public Animal{
public:
void eat(){
cout << "老虎吃肉..." <<endl;
}
};
class Pangolin : public Animal{
public:
void eat(){
cout << "穿山甲吃蚂蚁..." <<endl;
}
};
class Suckler : public Animal{};
int main() {
//Animal S = new Suckler; 报错, Allocating an object of abstract class type 'Suckler'
Animal * B = new Bear;
B->eat();
Animal * T = new Tiger;
T->eat();
Animal * P = new Pangolin;
P->eat();
return 0;
}
- 注意点: