C++ 中组合关系与继承的区别

2025年3月24日 | 阅读 8 分钟

在 C++ 中,继承组合(也称为包含)是描述类之间关系的两个基本概念。然而,它们有着不同的功能和对程序结构和设计的独特影响。

在本文中,您将了解 C++ 中的组合和继承。但在讨论它们的区别之前,您必须先了解组合和继承的语法、特性和示例。

什么是组合?

在一个类中创建另一个类的对象的行为称为组合包含。被使用的类称为“被包含”“组件”类,而包含对象的类称为“容器”“复合”类。

语法

它具有以下语法:

参数

  • 组件类:ComponentClass 代表一个组件,它具有自己的属性和函数集合。它包含特定的特征或动作。
  • 容器类:ContainerClass 代表类的组合,它展示了 ComponentClass 对象作为私有成员(componentObject)的存在。

组合的特性

C++ 中的组合有几个特性。组合的一些主要特性如下:

  1. “has-a”关系:组合表示类之间的“has-a”关系。容器类的一个成员是另一个类的对象。
  2. 代码重用性:它通过允许一个类使用另一个类的功能而不涉及继承的复杂性来鼓励代码重用。
  3. 灵活性:它提供了更大的灵活性,可以在运行时改变系统的行为。可以动态地创建和替换对象。
  4. 控制:被包含类对象的生命周期和初始化直接由容器类控制。
  5. 无自动向上转型:没有创建隐式类型层次结构,也没有自动向上转型。

示例

让我们举一个例子来说明 C++ 中的组合

输出

John is driving.
Car Sedan started.
Car Sedan stopped.
John parked the Car.

说明

  1. 在此示例中,Car 类代表一辆简单的汽车,具有型号。它有一个初始化汽车型号的构造函数。
  2. 它有两个成员函数 startstop,它们打印消息表明汽车已启动或停止。Driver 类代表一名司机,他有姓名并驾驶汽车。
  3. 之后,它有一个构造函数,该构造函数接受司机的姓名和汽车的型号,并初始化一个名为 Car 的 Car 对象。
  4. 驱动和停车的成员函数模拟了司机驾驶和停放汽车。drive 函数打印一条消息表明司机正在驾驶,然后调用关联 Car 对象的 start 函数。park 函数调用关联 Car 对象的 stop 函数,并打印一条消息表明司机已停放汽车。
  5. main() 函数中,创建了一个名为 john 的 Driver 对象,姓名为“John”,汽车型号为“Sedan”
  6. 之后,调用 john 对象的 drive 和 park 函数,模拟 John 驾驶和停放汽车。
  7. 程序通过从 main 函数返回 0 来结束。
  8. 此代码说明了 C++ 中的组合概念,其中 Driver 类包含 Car 类型的成员变量。它演示了司机和汽车之间的简单关系,其中司机可以执行驾驶和停放相关汽车等操作。

什么是继承?

继承是通过将一个现有类(基类父类)的特性和行为转移到一个新类(派生类或子类)来实现的。派生类增强或专门化了基类。

语法

它具有以下语法:

参数

  • class:表示类声明的关键字。
  • subclass_name:正在定义的派生类名称。
  • (冒号):它表示访问说明符和基类列表的开始。
  • base class name:这是正在继承的基类的名称。
  • {}:它包含派生类的成员和主体。

访问说明符

  • public:在派生类中,公共基类成员仍然是公共的。
  • protected:公共和受保护基类成员在派生类中获得保护。
  • private:在派生类中,公共和受保护基类成员变为私有的。

继承的特性

继承在 C++ 中有几个特性。继承的一些主要特性如下:

  1. “is-a”关系:继承是一个“is-a”关系的例子。基类的子类是派生类。
  2. 代码重用性:它通过允许派生类继承基类的成员(方法和属性)来鼓励代码重用。
  3. 多态性:它促进了多态性,允许在需要基类对象的地方使用派生类对象。
  4. 自动向上转型:此功能允许使用基类的指针或引用来引用派生类对象。
  5. 运行时绑定:它通过虚拟函数支持运行时绑定,从而实现动态分派。

示例

让我们举一个例子来说明 C++ 中的继承

输出

Engine started.
The vehicle is driving.
The Car is honking.

说明

  1. 在此示例中,程序定义了基类 Vehicle。StartEngine 和 drive 是它的两个公共成员操作。startEngine 函数打印一条消息表明发动机已启动,drive 函数打印一条消息表明车辆正在行驶。
  2. 之后,定义了一个名为 Car 的派生类,该类公有继承自 Vehicle 基类。这意味着 Car 类继承了 Vehicle 类的公共和受保护成员。Car 类引入的新公共成员函数 honk,它会产生一条消息表明汽车正在按喇叭。
  3. 在 main 函数中创建了一个名为 myCar 的 Car 类对象。接下来,程序通过使用基类(Vehicle)和派生类(Car)的成员函数来演示继承的用法,
  4. startEngine() 调用了从 Vehicle 基类继承的 startEngine 函数。
  5. 之后,MyCar 调用了从 Vehicle 基类继承的 drive 函数 drive()
  6. myCar.honk(); 调用了 Car 类独有的 honk 函数。
  7. 此输出表明 Car 类继承了 Vehicle 类的功能,并用其自身特有的功能进行了扩展。

组合与继承之间的主要区别

Difference between Containership and Inheritance in C++

组合继承在 C++ 中有几个区别。组合与继承之间的一些主要区别如下:

1. 关系性质

组合(Composition)

定义:一个类的对象可以存在于另一个类中。

关系:“has-a”关系,其中一个类的成员实例属于另一个类。

所有权:通常,容器类拥有被包含的项。

灵活性:它通过允许在运行时更改封装的项来提高灵活性。

继承

定义:一个类继承另一个类的特性和特征。

关系:派生类是基类的修改版本,如“is-a”连接所示。

所有权:没有所有权联系;派生类不对基类的生命周期负责。

灵活性:它通常不那么灵活,因为关系是在编译时建立的。

2. 代码可重用性

组合(Composition):它允许一个类包含其他类对象的成员,从而促进代码重用。

继承:继承通过允许一个类继承基类的特性和行为来促进代码重用。

3. 灵活性

组合(Composition):它提供了松耦合和灵活性。对象可以进行运行时修改和动态组合。

继承:继承建立了更僵化的结构。基类的修改可能会影响所有派生类。

4. 成员访问

组合(Composition):被包含类的成员不能直接在容器类之外访问;它们可以通过容器类访问。

继承:根据访问说明符(public、protected 或 private),基类的成员可以直接在派生类中访问。

5. 复杂性和耦合

组合(Composition):它通常通过减少类依赖性来实现更模块化的架构。

继承:继承可能导致更高的耦合,特别是当基类的修改影响多个派生类时。

6. 层级结构

组合(Composition):它形成一个扁平的层级结构,具有松散的类耦合。

继承:继承通过建立明确的父子关系来创建层级系统。

7. 静态与动态绑定

组合(Composition):它允许动态绑定,并且可能更适合在运行时进行的修改。

继承:继承支持编译时的静态绑定。多态性通过虚拟函数实现动态行为。

8. 封装

组合(Composition):组合通过将包含类中的细节包含在容器类中来实现。

继承:派生类可以访问基类的接口,这得益于继承。

9. 使用场景

组合(Composition):当您希望通过组合更简单的对象来构建对象,或者类之间不存在“is-a”关系时,这是最佳选择。

继承:当您希望模拟一个具有共享属性和行为以及明显“is-a”关系的层级结构时,继承会很有用。

10. 构造函数和析构函数调用

组合(Composition):每个类都有自己的构造函数和析构函数。

继承:由于继承,构造函数和析构函数会按照特定的顺序从基类调用到最派生的类。