目录
一、C++ 的基石:组合与继承
在 C++ 的编程世界里,组合(Composition)与继承(Inheritance)就像是大厦的基石,支撑起面向对象编程(OOP)的宏伟架构。它们不仅是代码复用的关键手段,更是理解 C++ 语言特性和编写高效、可维护代码的核心概念。
组合,简单来说,就是一个类中包含其他类的对象作为成员变量,体现了一种 “has - a”(有一个)的关系。比如,汽车类(Car)中包含引擎类(Engine)、轮胎类(Tire)的对象,因为一辆汽车 “有一个” 引擎和多个轮胎 。这种方式使得代码结构更加清晰,不同类的职责明确,通过组合不同的类,可以构建出复杂的系统。
继承,则是一个新类(派生类,Derived Class)可以获取已有类(基类,Base Class)的属性和方法,表现为 “is - a”(是一个)的关系。例如,轿车类(Sedan)继承自汽车类(Car),轿车 “是一种” 汽车,它自然拥有汽车的基本属性,如轮子数量、发动机等,同时还可以有自己特有的属性和方法,像更舒适的座椅、高级的导航系统等。继承为代码复用提供了一种层次化的结构,使得相似功能的代码可以被集中管理和维护。
组合和继承在 C++ 编程中无处不在,它们深刻影响着我们构建软件系统的方式。合理运用组合和继承,能够让我们的代码更加简洁、灵活和可扩展,极大地提高开发效率和代码质量。接下来,让我们深入探索这两个重要概念的细节、用法以及它们之间的区别和选择策略。
二、组合:构建代码的积木
(一)组合的概念
组合,是一种将其他类的对象作为新类的数据成员的方式,它体现了一种 “has - a”(有一个)的关系 。这种关系就像是搭积木,我们把不同的积木(类对象)组合在一起,形成一个更复杂的结构(新类)。
假设我们有一个表示有理数的Rational类,现在要构建一个表示复数的Complex类。复数由实部和虚部组成,而实部和虚部都可以用有理数来表示。这时,我们就可以在Complex类中组合两个Rational类的对象,分别表示实部和虚部。代码示例如下:
class Rational {
private:
int numerator; // 分子
int denominator; // 分母
public:
Rational(int num, int den) : numerator(num), denominator(den) {}
// 其他成员函数,如加法、减法等
};
class Complex {
private:
Rational realPart; // 实部
Rational imaginaryPart; // 虚部
public:
Complex(int realNum, int realDen, int imagNum, int imagDen)
: realPart(realNum, realDen), imaginaryPart(imagNum, imagDen) {}
// 其他成员函数,如复数加法、乘法等
};
在这个例子中,Complex类 “有一个”Rational类对象作为实部,“有一个”Rational类对象作为虚部,这就是组合关系的体现。通过组合,Complex类可以利用Rational类已有的功能,而无需重新实现。
(二)组合的实现细节
在使用组合时,有一个重要的实现细节需要注意,那就是对象成员的初始化。当一个类包含其他类的对象作为成员时,这些对象成员必须在初始化列表中进行初始化,而不能在构造函数体内进行赋值。这是因为在进入构造函数体之前,对象成员已经被默认构造了一次,如果在构造函数体内再进行赋值,就相当于进行了一次额外的操作,既降低了效率,又可能引发一些潜在的问题。
比如,我们有一个A类和一个B类,B类中包含A类的对象作为成员:
class A {
public:
A(int value) : data(value) {
cout << "A constructor with value: " << data << endl;
}
private:
int data;
};
class B {
public:
// 正确的初始化方式,使用初始化列表
B(int aValue) : memberA(aValue) {
cout << "B constructor" << endl;
}
// 错误的方式,不能在构造函数体内赋值
// B(int aValue) {
// memberA = A(aValue); // 错误,memberA先默认构造,再赋值
// cout << "B constructor" << endl;
// }
private:
A memberA;
};
在上述代码中,B类的构造函数通过初始化列表来初始化memberA,这样可以确保memberA在构造时就被正确初始化,避免了不必要的默认构造和赋值操作。
(三)组合的优势
组合在 C++ 编程中具有诸多优势。它实现了代码的复用。通过组合已有的类,我们可以快速构建新的类,而无需重复编写已有的功能。就像搭积木一样,我们可以利用现有的积木块搭建出各种不同的结构,而不需要重新制造每一块积木。以std::queue和std::deque的关系为例,std::queue是通过组合std::deque实现的,std::queue利用了std::deque的接口来实现队列的功能,从而复用了std::deque的代码,避免了重复实现底层的数据结构和操作。
组合有助于维持类的封装性。当我们使用组合时,被组合的类的内部细节对于组合类来说是隐藏的。组合类只需要通过被组合类的公共接口来使用其功能,而不需要了解其内部实现。这使得代码的维护和修改更加容易,因为我们可以独立地修改被组合类的内部实现,而不会影响到组合类。
组合还可以提高代码的灵活性。我们可以在运行时动态地改变组合关系,通过更换不同的对象成员,实现不同的功能。比如,一个图形绘制类可以组合不同的形状类对象,在运行时根据用户的选择绘制不同的图形,这种灵活性是继承所难以提供的。