C++ 继承
C++ 继承:构建层次结构
Section titled “C++ 继承:构建层次结构”继承是面向对象编程(OOP)的一个基本支柱。它允许一个新类(派生类)基于现有类(基类)。派生类继承基类的属性(数据成员)和行为(成员函数),从而促进代码重用,减少冗余,并创建逻辑上的“is-a”关系。
你可以通过继承基类的成员,然后在派生类中添加新成员或修改继承的成员,而不是从头开始。这建立了一个层次结构。例如,Dog 是一种 Mammal,而 Mammal 是一种 Animal。继承在代码中直接模拟了这些关系。
基类和派生类
Section titled “基类和派生类”派生类使用一个派生列表来指定其基类。
class DerivedClassName : accessSpecifier BaseClassName { // 派生类的成员};在这里,accessSpecifier 可以是 public、protected 或 private。如果省略,对于类(用 class 定义)默认为 private,对于结构体(用 struct 定义)默认为 public。public 继承是最常见的类型,它模拟了“is-a”关系。
让我们来看一个 Shape 基类和 Rectangle 派生类的示例:
#include <iostream>
// 基类class Shape {public: // 构造函数 Shape(int w = 0, int h = 0) : width(w), height(h) {}
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }
protected: // protected 成员可以被派生类访问 int width; int height;};
// 使用 public 继承的派生类class Rectangle : public Shape {public: // 调用基类构造函数的构造函数 Rectangle(int w = 0, int h = 0) : Shape(w, h) {}
int getArea() const { // 将方法标记为 const,因为它不修改对象 return (width * height); // 访问 Shape 的 protected 成员 }};
int main() { Rectangle rect(5, 7);
std::cout << "Initial Area: " << rect.getArea() << std::endl;
rect.setWidth(10); // 访问从 Shape 继承的 public 方法
std::cout << "Updated Area: " << rect.getArea() << std::endl;
return 0;}编译并执行此代码后,将产生以下输出:
Initial Area: 35Updated Area: 70访问控制和继承
Section titled “访问控制和继承”基类成员如何被继承取决于派生过程中使用的 accessSpecifier:
- 公共继承 (
public): 基类的public成员在派生类中仍然是public。基类的protected成员在派生类中仍然是protected。基类的private成员会被继承但派生类不能直接访问(尽管可以通过基类的公共或保护方法间接访问)。这是最常见的类型,表示“is-a”关系。 - 保护继承 (
protected): 基类的public和protected成员在派生类中都变成protected。private成员仍然不可访问。 - 私有继承 (
private): 基类的public和protected成员在派生类中都变成private。private成员仍然不可访问。这模拟的是“is-implemented-in-terms-of”(基于…实现)的关系,而不是“is-a”关系。
以下是访问级别的总结:
| 访问来源 | public 成员 | protected 成员 | private 成员 |
|---|---|---|---|
| 同类 | 是 | 是 | 是 |
| 派生类(通过公共继承) | 是 | 是 | 否 |
| 外部类(对象) | 是 | 否 | 否 |
关于继承的重要注意事项:
- 构造函数和析构函数不会被继承,但除非在初始化列表中指定了其他基类构造函数,派生类构造函数会自动调用基类的默认构造函数。
- 赋值运算符(
operator=)不会被继承。 - 基类的友元函数和友元类不会被继承。
- 如果你计划通过基类指针删除派生类对象,将基类析构函数声明为
virtual是至关重要的,以防止对象切片和资源泄露。示例:virtual ~Shape() = default;
覆盖方法和 override 关键字
Section titled “覆盖方法和 override 关键字”派生类可以提供基类成员函数自己的实现。这称为覆盖(或称重写)。对于基类中的 virtual 函数,强烈建议在派生类中使用 override 说明符。它有助于编译器验证你确实正在覆盖基类的虚函数,而不是签名错误。
class Base {public: virtual void print() const { std::cout << "Base\n"; } virtual ~Base() = default; // 虚析构函数};
class Derived : public Base {public: // 正确覆盖了 Base::print void print() const override { std::cout << "Derived\n"; }};final 说明符
Section titled “final 说明符”你可以通过将类标记为 final 来阻止其进一步被继承。你也可以通过将特定的 virtual 函数标记为 final 来阻止其在继承体系中进一步被覆盖。
class CannotDerive final : public Base { /* ... */ };
class BaseWithFinalMethod {public: virtual void cannotOverride() final { /* ... */ } virtual ~BaseWithFinalMethod() = default;};C++ 允许一个派生类继承自多个基类。
class DerivedClassName : accessSpecifier BaseA, accessSpecifier BaseB { // ...};考虑一个 Rectangle,它继承了 Shape 的属性和 PaintCost 的成本:
#include <iostream>
// 基类 1class Shape {public: Shape(int w = 0, int h = 0) : width(w), height(h) {} virtual ~Shape() = default; // 虚析构函数
void setWidth(int w) { width = w; } void setHeight(int h) { height = h; }
protected: int width; int height;};
// 基类 2class PaintCost {public: virtual ~PaintCost() = default; // 虚析构函数 int getCost(int area) const { return area * 70; // 每单位面积成本 }};
// 从两者继承的派生类class PaintedRectangle : public Shape, public PaintCost {public: PaintedRectangle(int w = 0, int h = 0) : Shape(w, h) {}
int getArea() const { return (width * height); }};
int main() { PaintedRectangle rect(5, 7); int area = rect.getArea();
std::cout << "Total area: " << area << std::endl; std::cout << "Total paint cost: $" << rect.getCost(area) << std::endl;
return 0;}输出:
Total area: 35Total paint cost: $2450注意: 如果两个基类继承自一个共同的祖先类,多重继承可能导致“菱形问题”(Diamond Problem)。这种歧义需要仔细解决,通常使用虚继承(class BaseA : virtual public Grandparent)。虽然多重继承功能强大,但应谨慎使用。