跳到主要内容

C++ 继承与多态

继承和多态是面向对象编程的核心特性。继承允许我们基于已有类创建新类,多态让我们可以用统一的接口处理不同类型的对象。

继承基础

什么是继承?

继承是面向对象程序设计中代码复用的一种机制。新类(派生类)可以继承已有类(基类)的成员:

// 基类(父类)
class Animal {
public:
std::string name;

void eat() {
std::cout << name << " is eating" << std::endl;
}

void sleep() {
std::cout << name << " is sleeping" << std::endl;
}
};

// 派生类(子类)
class Dog : public Animal {
public:
void bark() {
std::cout << name << " is barking" << std::endl;
}
};

int main() {
Dog dog;
dog.name = "Buddy";

dog.eat(); // 继承自 Animal
dog.sleep(); // 继承自 Animal
dog.bark(); // Dog 特有

return 0;
}

继承语法

class 派生类名 : 继承方式 基类名 {
// 新增成员
};

继承方式:

继承方式基类 public 成员基类 protected 成员基类 private 成员
public变为 public变为 protected不可访问
protected变为 protected变为 protected不可访问
private变为 private变为 private不可访问

继承示例:几何图形

// 基类
class Shape {
protected:
std::string color;

public:
Shape(const std::string& c) : color(c) {}

virtual double area() const = 0; // 纯虚函数

virtual void draw() const {
std::cout << "Drawing a " << color << " shape" << std::endl;
}
};

// 派生类:圆形
class Circle : public Shape {
private:
double radius;

public:
Circle(double r, const std::string& c)
: Shape(c), radius(r) {}

double area() const override {
return 3.14159 * radius * radius;
}

void draw() const override {
std::cout << "Drawing a " << color
<< " circle with radius " << radius << std::endl;
}
};

// 派生类:矩形
class Rectangle : public Shape {
private:
double width;
double height;

public:
Rectangle(double w, double h, const std::string& c)
: Shape(c), width(w), height(h) {}

double area() const override {
return width * height;
}

void draw() const override {
std::cout << "Drawing a " << color
<< " rectangle " << width << "x" << height << std::endl;
}
};

构造函数与析构函数

构造顺序

class Base {
public:
Base() {
std::cout << "Base 构造函数" << std::endl;
}
~Base() {
std::cout << "Base 析构函数" << std::endl;
}
};

class Derived : public Base {
public:
Derived() {
std::cout << "Derived 构造函数" << std::endl;
}
~Derived() {
std::cout << "Derived 析构函数" << std::endl;
}
};

int main() {
Derived d;
return 0;
}

// 输出:
// Base 构造函数
// Derived 构造函数
// Derived 析构函数
// Base 析构函数

结论: 构造顺序:基类 → 成员 → 派生类;析构顺序:派生类 → 成员 → 基类

基类构造函数

class Base {
private:
int value;

public:
Base(int v) : value(v) {
std::cout << "Base 构造函数: " << value << std::endl;
}
};

class Derived : public Base {
private:
int extra;

public:
// 显式调用基类构造函数
Derived(int v, int e) : Base(v), extra(e) {
std::cout << "Derived 构造函数: " << extra << std::endl;
}
};

int main() {
Derived d(10, 20);
return 0;
}

多态

虚函数

使用 virtual 关键字声明虚函数,实现运行时多态:

class Animal {
public:
// 虚函数:允许派生类重写
virtual void speak() {
std::cout << "Animal speaks" << std::endl;
}

// 非虚函数
void sleep() {
std::cout << "Animal sleeps" << std::endl;
}
};

class Dog : public Animal {
public:
// 重写基类的虚函数
void speak() override {
std::cout << "Dog barks: Woof!" << std::endl;
}
};

class Cat : public Animal {
public:
void speak() override {
std::cout << "Cat meows: Meow!" << std::endl;
}
};

int main() {
// 基类指针指向派生类对象
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();

animal1->speak(); // 输出: Dog barks: Woof!
animal2->speak(); // 输出: Cat meows: Meow!

// 注意:非虚函数不会表现出多态性
animal1->sleep(); // 输出: Animal sleeps

delete animal1;
delete animal2;

return 0;
}

override 说明符(C++11)

使用 override 明确表明函数是重写基类的虚函数:

class Base {
public:
virtual void func(int) {}
virtual void foo() const {}
};

class Derived : public Base {
public:
void func(int) override {} // 正确:重写
// void func(double) override {} // 错误:签名不匹配
// void func(int) const override {} // 错误:const 不匹配

void foo() const override {} // 正确
};

纯虚函数与抽象类

纯虚函数没有实现,派生类必须重写:

// 抽象类:包含纯虚函数的类
class Shape {
public:
// 纯虚函数
virtual double area() const = 0;
virtual void draw() const = 0;

// 普通函数
virtual ~Shape() {} // 虚析构函数
};

// 抽象类不能实例化
// Shape s; // 错误!

// 派生类必须实现所有纯虚函数
class Circle : public Shape {
private:
double radius;

public:
Circle(double r) : radius(r) {}

double area() const override {
return 3.14159 * radius * radius;
}

void draw() const override {
std::cout << "Drawing circle" << std::endl;
}
};

int main() {
Circle c(5);
std::cout << "Area: " << c.area() << std::endl;
c.draw();

// 可以使用基类指针
Shape* shape = new Circle(3);
std::cout << "Area: " << shape->area() << std::endl;
delete shape;

return 0;
}

虚函数表

理解虚函数的实现机制:

┌────────────────────────────────────────────────────────────┐
│ 虚函数表 (vtable) │
├────────────────────────────────────────────────────────────┤
│ │
│ Animal 对象: Dog 对象: │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ vptr ────────┼──→┌────────┐│ vptr ────────┼──→┌─────┐│
│ └──────────────┘ │Animal::││ └──────────────┘ │Dog::││
│ │ speak() ││ │speak││
│ └─────────┘│ └─────┘│
│ │
└────────────────────────────────────────────────────────────┘

关键点:

  1. 每个包含虚函数的类都有一个虚函数表
  2. 对象中有一个指针(vptr)指向虚函数表
  3. 运行时通过 vptr 查找并调用正确的函数

抽象类与接口

抽象类示例:形状系统

// 形状接口(抽象类)
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual void draw() const = 0;
};

// 实现形状接口
class Rectangle : public Shape {
private:
double width, height;

public:
Rectangle(double w, double h) : width(w), height(h) {}

double area() const override { return width * height; }
double perimeter() const override { return 2 * (width + height); }
void draw() const override {
std::cout << "Drawing rectangle" << std::endl;
}
};

class Circle : public Shape {
private:
double radius;

public:
Circle(double r) : radius(r) {}

double area() const override { return 3.14159 * radius * radius; }
double perimeter() const override { return 2 * 3.14159 * radius; }
void draw() const override {
std::cout << "Drawing circle" << std::endl;
}
};

// 使用多态
void printShapeInfo(const Shape& shape) {
std::cout << "Area: " << shape.area() << std::endl;
std::cout << "Perimeter: " << shape.perimeter() << std::endl;
}

接口与多重继承

C++ 没有接口关键字,但可以用抽象类模拟:

// 接口1
class Printable {
public:
virtual ~Printable() = default;
virtual void print() const = 0;
};

// 接口2
class Comparable {
public:
virtual ~Comparable() = default;
virtual bool operator<(const Comparable&) const = 0;
};

// 类实现多个接口
class Student : public Printable, public Comparable {
private:
std::string name;
int score;

public:
Student(const std::string& n, int s) : name(n), score(s) {}

void print() const override {
std::cout << name << ": " << score << std::endl;
}

bool operator<(const Comparable& other) const override {
const Student& otherStudent = dynamic_cast<const Student&>(other);
return score < otherStudent.score;
}
};

多态的应用

容器存储多态对象

#include <vector>

int main() {
std::vector<Shape*> shapes;

shapes.push_back(new Circle(5));
shapes.push_back(new Rectangle(4, 6));
shapes.push_back(new Circle(3));

// 统一调用
for (const auto* shape : shapes) {
shape->draw();
std::cout << "Area: " << shape->area() << std::endl;
}

// 释放内存
for (auto* shape : shapes) {
delete shape;
}

return 0;
}

使用 unique_ptr 管理内存

#include <memory>
#include <vector>

int main() {
std::vector<std::unique_ptr<Shape>> shapes;

shapes.push_back(std::make_unique<Circle>(5));
shapes.push_back(std::make_unique<Rectangle>(4, 6));

for (const auto& shape : shapes) {
shape->draw();
}

// unique_ptr 自动管理内存
return 0;
}

基类与派生类的转换

上转(Upcasting)

派生类指针转换为基类指针(安全):

Dog* dog = new Dog();
Animal* animal = dog; // 隐式转换

下转(Downcasting)

基类指针转换为派生类指针(需要显式转换,可能不安全):

Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // 转换成功

Animal* animal2 = new Animal();
Dog* dog2 = dynamic_cast<Dog*>(animal2); // 转换失败,返回 nullptr

注意: 向下转换前应使用 dynamic_cast 检查类型。

练习

  1. 设计一个 Vehicle 基类和 Car、Bicycle 派生类
  2. 实现一个图形绘制系统,使用多态绘制不同图形
  3. 使用抽象类实现一个排序器的接口
  4. 创建一个员工管理系统,经理继承自员工

小结

本章学习了:

  1. 继承:派生类继承基类的成员
  2. 构造函数与析构函数:继承中的构造顺序
  3. 虚函数:运行时多态的基础
  4. 纯虚函数与抽象类:接口定义
  5. 多态的应用:容器、指针、智能指针

下一步

接下来让我们学习 C++ 模板与泛型编程,了解代码复用的高级技巧。