C++中实现多态有几种方式小结
多态的概念
多态 指的是 具有继承关系 的不同对象在调用同一函数 时形成的不同状态!
举个例子:比如我们平时买高铁/火车票时,不同的人买票买的结果都是不一样的!学生是半价、成人是全价、军人是军人优先票 等!而这三种对象本质都是继承自同一个 Person 的父类,所以他们执行同一操作时不同的结果就是多态!
• 构成多态的两个必要条件
1、必须通过基类/父类的 指针或者引用 调用虚函数
2、派生类必须得对基类的虚函数进行重写
举个栗子先见一见:
class Person { public: // 虚函数 virtual void BuyTicket() { cout << "Person::买票-成人-全价" << endl; } }; class Student : public Person // 构成继承关系 { public: // 虚函数 virtual void BuyTicket() { cout << "Student::买票-学生-半价" << endl; } }; int main() { // 父类对象的指针 调用虚函数 Person* pp = new Student; pp->BuyTicket(); // 父类对象的引用 调用子类的对象 Student st; Person& rp = st; rp.BuyTicket(); Person p; Person& r = p;// 父类对象的引用 调用虚函数 r.BuyTicket(); Person* ptr = &p;// 父类对象的指针 调用虚函数 ptr->BuyTicket(); return 0; }
此时,不同的对象去以父类的指针/引用去调用虚函数时结果是不一样的
下面由小编给大家总结一下C++实现多态的几种方式!
一)虚函数(Virtual Functions)实现多态
概念:
虚函数是在基类中使用关键字virtual声明的成员函数。当一个类包含虚函数时,编译器会为该类创建一个虚函数表(v - table),这个表存储了虚函数的地址。当通过基类指针或引用调用虚函数时,程序会根据对象的实际类型(即指针或引用所指向或引用的实际子类对象)来查找虚函数表,从而调用子类中重写后的虚函数,实现多态行为。
代码示例:
class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; class Rectangle : public Shape { public: void draw() override { std::cout << "Drawing a rectangle." << std::endl; } };
在这个例子中,Shape是一个抽象基类,draw是一个纯虚函数。Circle和Rectangle是Shape的子类,它们重写了draw函数。当使用
Shape* shapePtr;
shapePtr = new Circle();
shapePtr->draw();
或者
Shape circleObj;
Shape& shapeRef = circleObj;
shapeRef.draw();
(circleObj是Circle类的对象)这样的方式调用draw函数时,会根据对象是Circle还是Rectangle来动态地调用相应子类的draw函数,实现多态。
二)函数指针实现多态(较少使用,但在某些特定场景下有效)
概念:
可以通过定义函数指针,让函数指针指向不同的函数实现来达到类似多态的效果。函数指针可以根据具体的情况(如运行时的条件)来改变它所指向的函数,从而实现不同的行为。不过这种方式与虚函数相比,没有自动的动态绑定机制,需要手动管理函数指针的指向。
代码示例:
// 定义函数指针类型,该函数接受无参数,返回void typedef void (*DrawFunction)(); class Shape { public: DrawFunction drawFunction; Shape(DrawFunction df) : drawFunction(df) {} void draw() { drawFunction(); } }; void drawCircle() { std::cout << "Drawing a circle using function pointer." << std::endl; } void drawRectangle() { std::cout << "Drawing a rectangle using function pointer." << std::endl; } int main() { Shape circleShape(drawCircle); Shape rectangleShape(drawRectangle); circleShape.draw(); rectangleShape.draw(); return 0; }
在这个例子中,Shape类中有一个DrawFunction类型的函数指针drawFunction。在构造函数中,可以传入不同的函数来初始化这个函数指针。当调用draw方法时,就会执行函数指针所指向的函数。通过这种方式,可以实现根据不同的对象(这里通过不同的构造方式)来执行不同的绘制函数,达到类似多态的效果。不过这种方式需要手动设置函数指针,而且对于继承体系的支持不如虚函数方便。
代码示例:
#include <iostream> // 定义函数指针类型,该函数接受两个整数参数并返回一个整数 typedef int (*MathOperation)(int, int); int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { MathOperation operation; operation = add; int result1 = operation(5, 3); operation = subtract; int result2 = operation(5, 3); std::cout << "加法结果: " << result1 << std::endl; std::cout << "减法结果: " << result2 << std::endl; return 0; }
在这个例子中,MathOperation是一个函数指针类型。通过将operation函数指针先后指向add函数和subtract函数,实现了根据需要调用不同函数的效果,从而在一定程度上实现了多态。
三)模板(Templates)实现编译时多态(也称为参数化多态)
概念:
模板是 C++ 中的泛型编程机制。虽然它不是真正的多态(因为它是在编译时确定具体的函数或类的版本,而不是运行时),但在某些情况下可以实现类似多态的代码复用和灵活性。通过模板,可以编写通用的代码,这些代码可以根据不同的类型参数生成不同的具体实现,从而适应多种数据类型或类类型的需求。
代码示例(函数模板):
template<typename T> void draw(T& shape) { shape.draw(); } class Circle { public: void draw() { std::cout << "Drawing a circle using template." << std::endl; } }; class Rectangle { public: void draw() { std::cout << "Drawing a rectangle using template." << std::endl; } }; int main() { Circle circle; Rectangle rectangle; draw(circle); draw(rectangle); return 0; }
在这个例子中,draw是一个函数模板,它可以接受不同类型的参数(只要这个类型有draw方法)。当传入Circle或Rectangle对象时,会在编译时根据对象的类型生成对应的draw函数调用,实现了对不同类型对象的通用处理,有点类似于多态的效果,但这种方式是基于编译时的类型推导,而不是像虚函数那样的运行时多态。
四)抽象基类与纯虚函数结合实现多态(与虚函数方式紧密相关)
概念:
抽象基类是包含纯虚函数的类,不能被实例化。纯虚函数是在基类中声明但没有定义的虚函数,它强制子类必须重写这个函数。通过这种方式,可以定义一个通用的接口,子类必须实现这个接口,从而实现多态。当通过基类指针或引用调用这些纯虚函数时,会根据子类的实际实现来调用相应的函数。
代码示例(与前面虚函数示例结合):
class Shape { public: virtual void draw() = 0; };
这里Shape是抽象基类,draw是纯虚函数。子类必须重写draw函数才能实例化,这样就确保了在通过Shape基类指针或引用调用draw函数时,能够根据具体子类的实现来获得不同的绘制行为,实现多态。这种方式明确了接口规范,并且和虚函数的动态绑定机制一起,是 C++ 实现多态的重要方式之一。
五)函数重载(Function Overloading)实现有限的多态性
概念:
函数重载是指在同一个作用域内,可以有多个同名函数,它们的参数列表(参数个数、类型、顺序)不同。当调用一个重载函数时,编译器会根据传入的实际参数来确定调用哪一个具体的函数版本。这种方式在一定程度上实现了多态性,因为相同的函数名可以根据不同的参数类型执行不同的操作。
代码示例:
#include <iostream> class Calculator { public: int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } }; int main() { Calculator calculator; int intResult = calculator.add(3, 5); double doubleResult = calculator.add(3.5, 2.5); std::cout << "整数相加结果: " << intResult << std::endl; std::cout << "浮点数相加结果: " << doubleResult << std::endl; return 0; }
在这个例子中,Calculator类中有两个add函数,一个用于整数相加,一个用于浮点数相加。编译器会根据传入add函数的参数类型来决定调用哪个版本,这就像一种简单的多态,根据参数类型的不同选择不同的操作方式。不过,函数重载是在编译时确定调用的函数版本,而不是像虚函数那样在运行时动态绑定。
六)函数对象(仿函数,Functors):
原理:函数对象是一个类,它重载了函数调用运算符operator()。这样,这个类的对象就可以像函数一样被调用。例如:
class AddFunctor { public: int operator()(int a, int b) const { return a + b; } }; class SubtractFunctor { public: int operator()(int a, int b) const { return a - b; } }; int main() { AddFunctor addObj; SubtractFunctor subtractObj; int result1 = addObj(3, 2); int result2 = subtractObj(3, 2); return 0; }
应用场景:在 STL 算法中广泛使用。例如,std::sort函数可以接受一个比较函数对象来确定排序的顺序。不同的比较函数对象可以实现不同的排序规则(如升序、降序),通过这种方式实现了基于不同规则的排序多态性。同时,函数对象可以携带状态(通过类的成员变量),这是函数指针所不具备的优势,在一些需要记录中间状态的场景中非常有用。
以上就是C++中实现多态有几种方式小结的详细内容,更多关于C++实现多态方式的资料请关注脚本之家其它相关文章!
相关文章
C++实现LeetCode(105.由先序和中序遍历建立二叉树)
这篇文章主要介绍了C++实现LeetCode(105.由先序和中序遍历建立二叉树),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下2021-07-07深入探讨linux下进程的最大线程数、进程最大数、进程打开的文件数
本篇文章是对linux下进程的最大线程数、进程最大数、进程打开的文件数进行了详细的分析介绍,需要的朋友参考下2013-05-05
最新评论