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││
│ └─────────┘│ └─────┘│
│ │
└────────────────────────────────────────────────────────────┘
关键点:
- 每个包含虚函数的类都有一个虚函数表
- 对象中有一个指针(vptr)指向虚函数表
- 运行时通过 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 检查类型。
练习
- 设计一个 Vehicle 基类和 Car、Bicycle 派生类
- 实现一个图形绘制系统,使用多态绘制不同图形
- 使用抽象类实现一个排序器的接口
- 创建一个员工管理系统,经理继承自员工
小结
本章学习了:
- 继承:派生类继承基类的成员
- 构造函数与析构函数:继承中的构造顺序
- 虚函数:运行时多态的基础
- 纯虚函数与抽象类:接口定义
- 多态的应用:容器、指针、智能指针
下一步
接下来让我们学习 C++ 模板与泛型编程,了解代码复用的高级技巧。