面向对象——多态、封装、继承、组合

面向对象

2.1 多态的实现方式

多态性主要通过两种方式实现:编译时多态(静态多态)和运行时多态(动态多态)

  • 静态多态:函数重载和运算符重载实现。 -> 编译期决定调用哪个函数
    • 函数重载:同一个作用域内存在多个同名函数,但它们的参数类型或数量不同;根据参数编译器决定调用哪个函数
    • 运算符重载:允许定义大部分C++内置的运算符,使得它们可以根据操作数的类型执行不同的操作。
  • 动态多态:通过虚函数和继承实现。 -> 运行时决定调用哪个函数。使用虚函数表(vtable)和动态绑定。

动态多态实现原理

  • 虚函数: 通过在基类中声明 virtual 函数,允许派生类中重写该函数。编译器生成**虚函数表(vtable)**来支持运行时的动态绑定。
  • 虚函数表(vtable)
    • 每个包含虚函数的类都会有一个虚函数表(vtable),它是一个存储指向虚函数实现的指针数组。表中的每个指针指向该类及其派生类的虚函数实现
    • 虚表指针(vptr):
      • 每个对象包含一个隐藏的指针(vptr),指向所属类的虚函数表。
      • 对象构造时,vptr 被初始化为指向对应类的虚函数表。

动态绑定的过程

  • 当通过基类指针或引用调用虚函数时:
    1. 编译器通过对象的虚表指针(vptr ) 找到对应的虚函数表(vtable)。
    2. 在虚函数表(vtable)中查找虚函数实现的函数指针。
    3. 调用实际的函数实现。
//示例虚函数
class Base {
public:
    virtual void func() { cout << "Base::func" << endl; } // 虚函数
    virtual ~Base() {} // 虚析构函数
};

class Derived : public Base {
public:
    void func() override { cout << "Derived::func" << endl; } // 重写虚函数
};

int main() {
    Base* ptr = new Derived(); // 基类指针指向派生类对象
    ptr->func(); // 调用 Derived::func,动态绑定
    delete ptr;
    return 0;
}

当我们调用ptr->func();时:基类指针ptr指向Derived对象,并通过ptrvptr虚表指针找到Derived虚表,最后在虚表中找到func()的实现。

2.1.1 虚函数是怎么实现的?

虚函数的实现依赖于两个核心概念:**虚函数表(vtable)**和 虚表指针(vptr)

虚函数表(vtable):每个包含虚函数的类都有一张虚函数表,它是一个存储函数指针的数组。存储了当前类及其派生类的所有虚函数的实现地址。派生类会继承基类的虚函数表,并根据需要覆盖其中的函数指针。

虚表指针(vptr):每个包含虚函数的对象都有一个隐藏成员,称为虚表指针vptr)。在对象创建时,vptr 被初始化为指向该类的虚函数表。当调用虚函数时,编译器通过 vptr 查找虚函数表中的函数指针,然后调用实际的函数实现。

2.2 封装、继承、多态、组合

封装

将数据和操作数据的方法绑定在一起,对外隐藏对象的内部实现细节,只暴露必要的接口。

特点

  • 数据隐藏:类的私有成员变量和方法对外部不可见。
  • 接口公开:通过公共方法访问和修改内部数据。
  • 提高代码的安全性和可维护性。

继承

用于在类之间建立层次结构,使子类可以复用父类的属性和行为。

特点

  • “is - a”关系:子类和父类具有明确的层次结构和共性
  • 代码复用:子类继承基类的公共代码,减少重复代码
  • GUI 系统中,QWidget 是所有控件的基类。

继承可能导致过度耦合,应优先考虑组合(“has-a”)而非继承

多态

允许程序在运行时根据实际对象类型执行不同的行为

特点

  • 动态行为切换:行为依赖于对象的实际类型
  • 接口统一:定义一组统一的接口供不同实现使用
  • 解耦:调用方无需了解具体子类类型

组合

通过将对象组合成更复杂的对象来实现功能,而不是依赖继承。

特点

  • “has-a”关系:对象包含另一个对象。
  • 更灵活,避免了继承导致的高耦合。

你可能感兴趣的:(C++,c++,开发语言)