Skip to content

C++ 继承

继承是面向对象编程(OOP)的一个基本支柱。它允许一个新类(派生类)基于现有类(基类)。派生类继承基类的属性(数据成员)和行为(成员函数),从而促进代码重用,减少冗余,并创建逻辑上的“is-a”关系。

你可以通过继承基类的成员,然后在派生类中添加新成员或修改继承的成员,而不是从头开始。这建立了一个层次结构。例如,Dog 是一种 Mammal,而 Mammal 是一种 Animal。继承在代码中直接模拟了这些关系。

派生类使用一个派生列表来指定其基类。

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: 35
Updated Area: 70

基类成员如何被继承取决于派生过程中使用的 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;

派生类可以提供基类成员函数自己的实现。这称为覆盖(或称重写)。对于基类中的 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 来阻止其进一步被继承。你也可以通过将特定的 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>
// 基类 1
class 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;
};
// 基类 2
class 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: 35
Total paint cost: $2450

注意: 如果两个基类继承自一个共同的祖先类,多重继承可能导致“菱形问题”(Diamond Problem)。这种歧义需要仔细解决,通常使用虚继承(class BaseA : virtual public Grandparent)。虽然多重继承功能强大,但应谨慎使用。