C++中的接口继承和实现继承以及多态性与性能的平衡处理

news/2024/12/16 11:08:53/

接口继承

接口继承是指子类只继承基类的纯虚函数,即只继承基类的接口,而不继承基类的实现。子类必须实现基类中的所有纯虚函数,否则子类也将成为抽象类。在 C++ 中,接口继承主要通过抽象类来实现。抽象类是包含至少一个纯虚函数的类。纯虚函数是在基类中声明但没有定义的虚函数,用 “= 0” 来标记。例如:

下面定义了一个抽象类Shape,它就像是一个接口,规定了派生类必须实现area和draw函数


class Shape {
public:virtual double area() = 0;virtual void draw() = 0;
};

当一个类继承自抽象类时,它必须实现抽象类中的所有纯虚函数,否则这个派生类也会成为抽象类。例如,定义一个Circle类继承自Shape

 class Circle : public Shape {private:double radius;public:Circle(double r) : radius(r) {}double area() override {return 3.14159 * radius * radius;}void draw() override {drawCircle(radius);}};

接口继承的重点在于定义规范,强制派生类提供符合接口要求的实现,从而使不同的派生类能够以统一的方式被使用,这有利于实现多态性。

实现继承

实现继承是指子类不仅继承基类的接口,还继承基类的实现。子类可以选择重写基类的函数,也可以直接使用基类的函数实现。实现继承是指派生类继承基类的成员函数和成员变量的实现。在 C++ 中,通过普通的类继承机制实现。例如,有一个基类Vehicle

class Vehicle {
protected:int speed;
public:Vehicle(int s) : speed(s) {}void move() {cout << "Vehicle is moving at " << speed << " mph." << endl;}
};

然后定义一个派生类Car继承自Vehicle

  class Car : public Vehicle {public:Car(int s) : Vehicle(s) {}void move() {cout << "Car is moving at " << speed << " mph." << endl;}};

这里Car类继承了Vehicle类的speed成员变量和move函数的实现。Car类可以选择重写move函数,也可以不重写而直接使用Vehicle类中的move函数实现。

实现继承侧重于代码复用。派生类可以继承基类已经实现好的功能,减少代码的重复编写。同时,派生类还可以根据需要对继承的函数进行重写,以实现特定的行为。在上述例子中,Car类继承了Vehicle类的基本移动功能move,并在自己的move函数实现中复用了基类中的speed成员变量。

总结接口继承和实现继承的区别

1.继承内容:接口继承主要继承的是纯虚函数的接口规范,没有函数体的继承;实现继承则继承了基类的成员变量和成员函数的实际实现。

2.实现要求:接口继承要求派生类必须实现所有纯虚函数;实现继承中派生类可以选择重写或者不重写基类的函数。

3.设计目的:接口继承用于定义统一的行为规范,方便实现多态;实现继承用于代码复用和基于已有功能的扩展。

接口继承与实现继承在面向对象编程中的应用场景

接口继承:适用于定义一组通用的接口,让不同的类实现这些接口,以实现多态性。例如,在图形绘制系统中,可以定义一个抽象的图形接口类,让不同的具体图形类实现这个接口。

实现继承:适用于在已有类的基础上进行扩展和修改,继承基类的实现可以减少代码重复。例如,在一个游戏开发中,可能有一个基础的角色类,其他具体的角色类可以继承这个基础角色类的实现,并进行特定的扩展。

如何处理多态性与性能的平衡

多态性对性能的影响:

多态性通常通过虚函数实现,而虚函数的调用会带来一定的性能开销。这是因为在运行时确定要调用的具体函数需要通过虚函数表进行查找,相比直接调用非虚函数会稍微慢一些。此外如果大量使用多态性,可能会增加程序的内存占用,因为每个包含虚函数的类都需要维护一个虚函数表。

1.虚函数调用机制

C++ 中的多态性通过虚函数实现。当通过基类指针或引用调用虚函数时,程序需要在运行时查找虚函数表来确定要调用的实际函数。这个查找过程会产生一定的性能开销。例如:

class Base {
public:virtual void func() {cout << "Base::func" << endl;}
};
class Derived : public Base {
public:void func() override {cout << "Derived::func" << endl;}
};
int main(){Base* ptr = new Derived();ptr->func();delete ptr;return 0;
}

在这个例子中,当通过ptr(Base类指针)调用func函数时,编译器会在运行时根据ptr所指向对象的实际类型(Derived)从虚函数表中查找func函数的地址,这相比于直接调用非虚函数会更耗时。

2.内存开销

每个包含虚函数的类都需要维护一个虚函数表,这会增加内存占用。在大型程序中,如果有大量包含虚函数的类和对象,虚函数表所占用的内存可能会比较可观。

如何在面向对象编程中平衡多态性与性能:

1.谨慎使用多态性:只在真正需要多态行为的地方使用虚函数,避免不必要的多态调用。例如,如果一个函数在大多数情况下只需要调用基类的实现,可以将其定义为非虚函数。例如,在一个简单的图形绘制系统中,如果对于大多数基本形状(如简单的矩形、圆形),绘制函数的行为是固定的,那么可以将这个绘制函数在基类中定义为非虚函数,以避免虚函数调用的开销。

2.优化关键路径:对于性能关键的代码路径,可以考虑避免使用多态性,或者使用其他技术如模板元编程来实现类似的功能,以提高性能。模板是在编译时确定类型的,不会有运行时多态性的开销。例如,在一个数学计算库中,对于矩阵乘法这种性能关键的操作,如果矩阵类型是固定的几种(如整数矩阵、浮点数矩阵),可以使用模板来实现不同类型矩阵的乘法,而不是通过多态性。

3.考虑内联虚函数:如果虚函数的实现非常小,可以考虑将其声明为内联函数,这样可以减少函数调用的开销。但需要注意的是,内联是由编译器决定的,不一定能保证被内联。例如:对于getValue这样简单的虚函数,可以尝试将其声明为内联函数,但不能保证编译器一定会内联这个函数。

class S{public:virtual int getValue() {return 5;}};

举例说明在实际项目中如何平衡多态性与性能:

在一个游戏引擎中,对于频繁调用的游戏对象更新函数,如果性能要求非常高,可以考虑使用模板或者其他技术来代替多态性。而对于一些不太频繁调用的、需要根据不同类型的游戏对象执行不同行为的函数,可以使用多态性来实现。例如在游戏开发的角色系统中,假设有一个Character基类,有move、attack等虚函数用于实现角色的移动和攻击行为。对于频繁调用的update函数(用于更新角色状态),如果其行为在大多数角色类型中是相似的,可以将update函数定义为非虚函数,以避免虚函数调用的开销。而对于attack等函数,因为不同角色(如战士、法师)的攻击方式差异较大,需要多态性来实现不同的行为,这时可以通过合理的类设计和虚函数调用,同时注意对关键性能路径的优化,如在update函数中尽量减少虚函数调用,来平衡多态性和性能。


http://www.ppmy.cn/news/1555570.html

相关文章

Django ORM – 多表实例

表与表之间的关系可分为以下三种&#xff1a; 一对一: 一个人对应一个身份证号码&#xff0c;数据字段设置 unique。一对多: 一个家庭有多个人&#xff0c;一般通过外键来实现。多对多: 一个学生有多门课程&#xff0c;一个课程有很多学生&#xff0c;一般通过第三个表来实现关…

Spire.PDF for .NET【页面设置】演示:向 PDF 文档添加页码

在 PDF 文档中添加页码不仅实用&#xff0c;而且美观&#xff0c;因为它提供了类似于专业出版材料的精美外观。无论您处理的是小说、报告还是任何其他类型的长文档的数字副本&#xff0c;添加页码都可以显著提高其可读性和实用性。在本文中&#xff0c;您将学习如何使用Spire.P…

游戏引擎学习第47天

仓库: https://gitee.com/mrxiao_com/2d_game 昨天我们花了一点时间来修复一个问题&#xff0c;但基本上是在修复这个问题的过程中&#xff0c;我们决定添加一个功能&#xff0c;那就是在屏幕上控制多个实体。所以如果我有一个手柄&#xff0c;我可以添加另一个角色&#xff0…

java agent-03-Java Instrumentation 结合 bytekit 实战笔记 agent attach

java agent 系列 java agent 介绍 java agent-02-Java Instrumentation API java agent-03-Java Instrumentation 结合 bytekit 实战笔记 agent attach java agent-03-Java Instrumentation 结合 bytekit 实战笔记 agent premain 拓展阅读 前面几篇文档&#xff0c;我们简…

⭐Redis - 手动实现分布式锁 Redisson 的使用

概述 定义&#xff1a;分布式系统或集群模式下&#xff0c;多进程或多节点之间 “可见” 并且 “互斥” 的锁机制 功能&#xff1a;确保同一时刻只有一个进程或节点能够获取某项资源的访问权 特点 互斥高可用多进程可见高并发 (高性能)安全性 (避免死锁问题) 常见的分布式锁 …

【软件工程】第八章·单元/集成测试程序

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;软件开发必练内功_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…

vue2-请求代理,动态target

当你在 Vue 2 项目中将 axios 的 baseURL 配置为 http://192.168.11.111:8762 时&#xff0c;所有请求都被认为是绝对路径请求&#xff0c;这种请求会直接发送到目标服务器&#xff0c; 跳过开发服务器的代理。 baseURL具体值 这就是为什么代理配置无法拦截 /exportPdf 的原因…

商协会管理系统:沃德商协会管理系统微信小程序公众号

智慧化会员体系 在线入会、会费缴纳、到期提醒、会员管理、消息群发、线上证书、会员通讯录、有效供需匹配等。 智敏化内容运营活动接龙&#xff0c;问卷调查&#xff0c;党建新闻资讯发布&#xff0c;多方位满足会员内容信息运营。 智能化活动构建为商会提供多种活动营解决…