提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、多态是什么?
多态是面向对象编程中的一个重要概念,指的是同一个函数在不同的对象上具有不同的行为。具体来说,多态是通过虚函数实现的。
二、多态的种类:静态多态和动态多态
①静态多态:如函数重载与运算符重载
静态多态是指在编译时就确定了函数的调用方式,也称为编译时多态。C++中的函数重载就是一种静态多态,编译器在编译时就根据函数参数的类型和数量来确定调用哪个函数。
②动态多态:派生类与虚函数
动态多态是指在运行时根据对象的类型来确定函数的调用方式,也称为运行时多态。C++中的虚函数就是一种动态多态,当通过基类指针或引用调用虚函数时,实际上会根据指针或引用所指向的对象类型来调用相应的函数实现。
语法:基类对该多态函数用virtual声明。
三、函数重写与函数重载的区别
函数重写(override)和函数重载(overload)是两种不同的概念。
函数重载是指在同一个作用域内,可以定义多个同名函数,但是它们的参数列表不同。编译器会根据调用时提供的参数类型和数量,自动匹配合适的函数进行调用。函数重载可以提高代码的可读性和可维护性,但是需要注意避免过度使用,以免造成混淆。
函数重写是指在派生类中重新定义基类中已有的虚函数,使得派生类对象在调用该函数时,会优先调用派生类中的函数,而不是基类中的函数。函数重写可以实现运行时多态,提高代码的灵活性和可扩展性。
class animal {
public:
virtual void print() {
cout << "hello world" << endl;
}
};
class cat :public animal {
public:
void print() {//重写基类的成员函数
cout << "hello world cat" << endl;
}
void print(int n) {//重载成员函数
for (int i = 0; i < n; i++) {
cout << "hello world cat" << endl;
}
}
};
int main() {
animal a;
cat cat1;
a.print(); // 输出 "hello world"
cat1.print(); // 输出 "hello world cat"
cat1.print(3); // 输出三次 "hello world cat"
return 0;
}
四、通过基类的指针或引用来访问派生类对象(重要)
多态需要通过基类的指针或引用来访问派生类对象。通过将派生类对象赋值给基类指针或引用,可以实现对派生类对象的统一访问
什么意思呢?就是说基类会有很多个派生类,我不想通过一个个实例化派生类的对象来调用其各自的函数,而是想直接通过基类来统一的管理与调用。
class animal {
public:
virtual void sound() {
cout << "animal sound" << endl;
}
};
class cat :public animal {
public:
void sound() {
cout << "meow" << endl;
}
};
class dog :public animal {
public:
void sound() {
cout << "woof" << endl;
}
};
int main() {
animal* animals[3];
animals[0] = new animal();
animals[1] = new cat();
animals[2] = new dog();
for (int i = 0; i < 3; i++) {
animals[i]->sound();
}
return 0;
}
mian函数是核心,我直接建立了一个类animal类型的指针数组,指针0指向类animal,指针1和2分别指向类cat与dog。这样的话,才能保证不同指针调用的是不同类的多态函数。如果不加virtual修饰,则三个指针都会调用基类函数。
五、多态与虚函数的内部实现原理
多态的实现依赖于虚函数的内部实现原理。在面向对象编程中,虚函数是通过虚函数表(vtable)和虚函数指针(vptr)来实现多态的。
虚函数表是一个存储了虚函数地址的表格,每个包含虚函数的类都有一个对应的虚函数表。虚函数表中的每个条目都指向相应虚函数的地址。当一个类被定义为包含虚函数时,编译器会在该类的对象中插入一个隐藏的指针,称为虚函数指针(vptr)。虚函数指针指向该类的虚函数表。
当通过基类指针或引用调用虚函数时,编译器会使用虚函数指针来查找虚函数表,并根据对象的实际类型找到对应的虚函数地址进行调用。这样就实现了在运行时根据对象的实际类型来确定调用的函数,从而实现了多态。
六、多态有什么用?应用场合?
我举一个例子:
实际工作中,需要开发一个计算器的项目,如果我只定义一个计数器类,在该类里面写函数,可以使用多态来处理不同类型的数据对象。
开发中的原则上:要求修改是闭合的,拓展是开放的。
所以,对于一个基类,最好只是声明一个大纲,而正式的各种不同功能的实现,需要通过多态,在派生类中完成。
#include <iostream>
// 基类 Calculator
class Calculator {
public:
virtual double calculate(double num1, double num2) {
return 0;
}
};
// 加法计算器类
class AddCalculator : public Calculator {
public:
double calculate(double num1, double num2) {
return num1 + num2;
}
};
// 减法计算器类
class SubCalculator : public Calculator {
public:
double calculate(double num1, double num2) {
return num1 - num2;
}
};
int main() {
// 创建加法计算器对象
Calculator* addCalc = new AddCalculator();
double result1 = addCalc->calculate(5.0, 3.0);
std::cout << "加法计算结果: " << result1 << std::endl;
// 创建减法计算器对象
Calculator* subCalc = new SubCalculator();
double result2 = subCalc->calculate(5.0, 3.0);
std::cout << "减法计算结果: " << result2 << std::endl;
delete addCalc;
delete subCalc;
return 0;
}
我如果后续想对加法、减法进行修改或者拓展,就可以完全不管其他乘或除部分的代码,只负责这一个派生类即可。可以通过多态的方式,统一地操作不同类型的计算器对象,可读性更强了。
七、纯虚函数和抽象类
1.在多态中,父类的虚函数实现是没有意义的,调用的主要是子类中重写的内容,所以父类虚函数可改写为纯虚函数。
纯虚函数是在基类中声明但没有实现的虚函数。它的声明形式为在函数声明后面加上= 0。纯虚函数没有函数体,因此派生类必须实现它们才能被实例化。
例:
// 抽象类 Shape
class Shape {
public:
virtual double getArea() const = 0; // 纯虚函数
virtual double getPerimeter() const = 0; // 纯虚函数
};
2.只要一个类里有了纯虚函数,这个类就是抽象类了。抽象类无法被实例化对象。
3.子类必须重写抽象类的纯虚函数,否则也属于抽象类。
// 派生类 Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() const override {
return 3.14 * radius * radius;
}
double getPerimeter() const override {
return 2 * 3.14 * radius;
}
};
// 派生类 Rectangle
class Rectangle : public Shape {
private:
double length;
double width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double getArea() const override {
return length * width;
}
double getPerimeter() const override {
return 2 * (length + width);
}
};
主程序:
int main() {
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
std::cout << "圆的面积: " << circle.getArea() << std::endl;
std::cout << "圆的周长: " << circle.getPerimeter() << std::endl;
std::cout << "矩形的面积: " << rectangle.getArea() << std::endl;
std::cout << "矩形的周长: " << rectangle.getPerimeter() << std::endl;
return 0;
}