Bootstrap

C++中的继承与多态

在C++中,继承和多态是面向对象编程的核心概念,它们使得代码更加模块化、可重用和易于维护。

继承

继承是一种代码复用机制,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以获得父类的功能,并可以根据需要扩展或修改这些功能。

在C++中,继承主要分为三种类型:

  1. 公有继承(Public Inheritance):子类继承父类的公有成员和保护成员,但私有成员不可直接访问。公有继承表示“是一个”关系,即子类是一种特定类型的父类。

  2. 保护继承(Protected Inheritance):子类继承父类的公有成员和保护成员,并且这些成员在子类中仍然保持保护属性。私有继承表示子类只能访问父类的公有和保护成员。

  3. 私有继承(Private Inheritance):子类继承父类的公有成员和保护成员,但是这些成员在子类中成为私有的。私有继承表示实现细节的共享,而不是“是一个”的关系。

多态

多态是指允许不同子类对象被统一对待的特性。在C++中,多态主要通过虚函数实现。当一个基类指针或引用指向派生类对象时,通过虚函数调用可以根据对象的实际类型来执行相应的函数。

多态分为两种:

  1. 静态多态(Static Polymorphism):也称为编译时多态,主要通过函数重载和模板实现。编译器在编译时期根据函数参数的类型和数量来确定调用哪个函数。

  2. 动态多态(Dynamic Polymorphism):也称为运行时多态,主要通过虚函数实现。在运行时,根据对象的实际类型来决定调用哪个函数。

虚函数

  • 虚函数是在基类中声明的,它可以在派生类中被重写(override)。

  • 当基类指针或引用调用虚函数时,会发生动态绑定,即实际调用的是对象所属类的版本。

  • 虚函数的调用是多态的基础,它使得不同子类的对象可以通过相同的接口进行操作。

纯虚函数和抽象类

  • 纯虚函数是没有函数体的虚函数,它在基类中的声明以= 0结尾。

  • 拥有纯虚函数的类被称为抽象类,抽象类不能被实例化。

  • 抽象类通常作为基类,用于定义接口,其子类必须实现所有纯虚函数才能成为具体类。

构造函数和析构函数与继承

  • 构造函数不能被继承,每个子类都必须定义自己的构造函数。

  • 析构函数可以被继承,但如果子类没有重写析构函数,则会调用基类的析构函数。

  • 在多态场景下,正确的析构顺序很重要,通常应该先删除最派生的对象。

多态性的局限性

  • 多态性只适用于有继承关系的类。

  • 多态性要求至少有一个虚函数。

  • 通过基类指针或引用调用虚函数时,只能访问在基类中定义的接口。

案例一:公有继承

假设我们有一个基类Animal,它代表了所有动物共有的基本特征和行为。

class Animal {
public:
    void eat() {
        cout << "Eating..." << endl;
    }
    virtual void makeSound() {
        cout << "Making a generic animal sound..." << endl;
    }
};

现在,我们想要创建一个子类Dog,它继承自Animal,并添加了一些特有的行为。

class Dog : public Animal {
public:
    void bark() {
        cout << "Barking!" << endl;
    }
    void makeSound() override { // 覆盖基类的makeSound函数
        bark();
    }
};

在这个例子中,Dog类通过公有继承获得了Animal类的eat方法和makeSound方法。Dog类还添加了一个新的方法bark,并且覆盖了makeSound方法,使得当通过Dog对象调用makeSound时,执行的是Dog类的bark方法。

案例二:保护继承

假设我们有一个基类Vehicle,它代表了所有车辆的基本特征和行为。

class Vehicle {
protected:
    int speed;
public:
    Vehicle(int s) : speed(s) {}
    void accelerate(int increment) {
        speed += increment;
    }
};

现在,我们想要创建一个子类Car,它继承自Vehicle,并添加了一些特有的行为。

class Car : protected Vehicle {
public:
    Car(int s) : Vehicle(s) {}
    void honk() {
        cout << "Honking the horn!" << endl;
    }
};

在这个例子中,Car类通过保护继承获得了Vehicle类的速度和加速功能。由于速度是通过保护成员继承的,因此只有Car类及其子类可以访问速度属性。Car类还添加了一个新的方法honk

案例三:私有继承

假设我们有一个基类Shape,它代表了所有形状的基本特征和行为。

class Shape {
public:
    void draw() {
        cout << "Drawing a generic shape..." << endl;
    }
};

现在,我们想要创建一个子类Square,它继承自Shape,并添加了一些特有的行为。

class Square : private Shape {
public:
    void draw() {
        Shape::draw(); // 调用基类的draw方法
        cout << "Drawing a square..." << endl;
    }
};

在这个例子中,Square类通过私有继承获得了Shape类的draw方法。由于draw方法是通过私有成员继承的,因此它只能在Square类内部被访问。Square类还添加了一个新的方法draw,它首先调用了基类的draw方法,然后添加了绘制正方形的特定逻辑。

案例四:多态

假设我们有一个基类Animal和几个派生类DogCatBird,它们分别代表不同种类的动物。

class Animal {
public:
    virtual void makeSound() = 0; // 纯虚函数,使Animal成为抽象类
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Barking!" << endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        cout << "Meowing!" << endl;
    }
};

class Bird : public Animal {
public:
    void makeSound() override {
        cout << "Chirping!" << endl;
    }
};

现在,我们可以创建一个函数,它接受Animal类型的引用作为参数,并调用其makeSound方法。由于多态,实际调用的将是对象所属类的makeSound方法。

void playSound(Animal& animal) {
    animal.makeSound();
}

int main() {
    Dog dog;
    Cat cat;
    Bird bird;

    playSound(dog); // 输出: Barking!
    playSound(cat); // 输出: Meowing!
    playSound(bird); // 输出: Chirping!

    return 0;
}
;