C++ 继承(Inheritance)详解

90次阅读
没有评论

什么是继承?

继承是 C++ 面向对象编程的三大特性之一(另外两个是封装和多态)。它允许我们创建一个新类(称为派生类子类),从一个已有的类(称为基类父类)中继承属性和方法。

核心思想:代码重用和建立类之间的层次关系。

优势

  • 代码重用:子类可以重用父类的代码,无需重新编写。
  • 可扩展性:可以在子类中添加新的功能,扩展父类的能力。
  • 逻辑清晰:建立 “is-a”(是一个)的关系,例如,Car is-a Vehicle
  • 易于维护:修改基类的代码,所有派生类都会自动继承这些更改。

继承的语法

class DerivedClass : access_specifier BaseClass {
    // ... members of DerivedClass
};
  • DerivedClass:派生类名。
  • BaseClass:基类名。
  • access_specifier:访问修饰符,可以是 publicprotectedprivate。如果省略,默认为 private(对于 class)或 public(对于 struct)。

访问控制与继承方式

访问修饰符决定了基类成员在派生类中的访问级别。

继承方式 基类的 public 成员 基类的 protected 成员 基类的 private 成员
public 在派生类中是 public 在派生类中是 protected 在派生类中不可访问
protected 在派生类中是 protected 在派生类中是 protected 在派生类中不可访问
private 在派生类中是 private 在派生类中是 private 在派生类中不可访问
  • public 继承:最常用的方式。它建立了 “is-a” 的关系。派生类对象可以被当作基类对象使用。
  • protected 继承:基类的公有和保护成员都成为派生类的保护成员。
  • private 继承:基类的公有和保护成员都成为派生类的私有成员。这通常表示 “is-implemented-in-terms-of”(依据…来实现)的关系。

示例

class Vehicle {
public:
    void start() { /* ... */ }
protected:
    int speed;
private:
    int id;
};

class Car : public Vehicle {
public:
    void drive() {
        start();  // OK, start() is public in Car
        speed = 100; // OK, speed is protected in Car
        // id = 1;   // Error: id is private in Vehicle and not accessible
    }
};

int main() {
    Car myCar;
    myCar.start(); // OK, start() is public
    // myCar.speed = 50; // Error: speed is protected in Car, not accessible from outside
    return 0;
}

继承中的构造函数和析构函数

  • 构造函数

    • 子类不能继承父类的构造函数。
    • 创建子类对象时,会先调用父类的构造函数,然后再调用子类的构造函数。
    • 如果父类有带参数的构造函数,子类必须在构造函数的初始化列表中显式调用它。
  • 析构函数

    • 销毁子类对象时,会先调用子类的析构函数,然后再调用父类的析构函数(顺序与构造相反)。
    • 重要:当使用基类指针指向派生类对象时,为了确保派生类的析构函数被正确调用,基类的析构函数必须声明为虚函数 (virtual)

示例

#include 
<iostream>

class Base {
public:
    Base(int b) { std::cout << "Base constructor called with " << b << std::endl; }
    virtual ~Base() { std::cout << "Base destructor called" << std::endl; }
};

class Derived : public Base {
public:
    // Call Base's constructor in the initializer list
    Derived(int d, int b) : Base(b) {
        std::cout << "Derived constructor called with " << d << std::endl;
    }
    ~Derived() override { std::cout << "Derived destructor called" << std::endl; }
};

int main() {
    Base* ptr = new Derived(10, 20);
    delete ptr; // Correctly calls ~Derived() then ~Base() because of virtual destructor
    return 0;
}
// Output:
// Base constructor called with 20
// Derived constructor called with 10
// Derived destructor called
// Base destructor called

函数重写与多态

  • 函数重写 (Overriding):派生类可以提供一个与基类中某个虚函数(virtual function)具有相同签名(函数名、参数列表、const 属性)的新实现。
  • 虚函数 (virtual):在基类中用 virtual 关键字声明的函数。它告诉编译器,这个函数可能会在派生类中被重写,并且应该在运行时根据对象的实际类型来决定调用哪个版本(动态绑定)。
  • 多态 (Polymorphism):允许我们使用基类的指针或引用来调用派生类中重写的函数,从而实现 “一个接口,多种形态” 的效果。

C++11 关键字

  • override:在派生类函数后添加 override,可以让编译器检查该函数是否真的重写了基类的虚函数。如果不是,编译器会报错。这是一个很好的实践,可以防止拼写错误等问题。
  • final
    • 用于虚函数:表示该虚函数不能再被任何后续派生类重写。
    • 用于类:class MyClass final { ... }; 表示该类不能被继承。

示例

#include 
<iostream>

class Animal {
public:
    virtual void makeSound() const {
        std::cout << "Some generic animal sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void makeSound() const override { // 'override' ensures we are overriding a base virtual function
        std::cout << "Woof!" << std::endl;
    }
};

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

void playSound(const Animal& animal) {
    animal.makeSound(); // Polymorphic call
}

int main() {
    Dog dog;
    Cat cat;
    playSound(dog); // Outputs: Woof!
    playSound(cat); // Outputs: Meow!
    return 0;
}

抽象类与纯虚函数

  • 纯虚函数 (Pure Virtual Function):一个没有实现的虚函数,通过在函数声明末尾加上 = 0 来定义。
  • 抽象类 (Abstract Class):包含至少一个纯虚函数的类。
    • 不能被实例化(不能创建对象)。
    • 主要用作接口,强制派生类必须实现其纯虚函数。如果派生类没有实现所有纯虚函数,那么它自己也成为一个抽象类。

示例

class Shape { // Abstract class
public:
    virtual double getArea() const = 0; // Pure virtual function
    virtual ~Shape() = default;
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    double getArea() const override { // Must implement
        return width * height;
    }
};

int main() {
    // Shape s; // Error: cannot instantiate abstract class
    Rectangle r(10, 5);
    Shape* shape_ptr = &r;
    std::cout << "Area: " << shape_ptr->getArea() << std::endl; // Outputs: Area: 50
    return 0;
}

多重继承与菱形问题

  • 多重继承 (Multiple Inheritance):一个派生类可以同时从多个基类继承。

    class Derived : public Base1, public Base2 {
        // ...
    };
  • 菱形问题 (Diamond Problem):当两个类(如 BC)都从同一个基类(A)继承,而另一个类(D)又同时从 BC 继承时,D 中会包含两份来自 A 的成员,导致歧义。

        A
       / \
      B   C
       \ /
        D
  • 虚继承 (Virtual Inheritance):用于解决菱形问题。通过在继承时使用 virtual 关键字,可以确保在派生类中只保留一份共享基类的成员。

示例

class A {
public:
    int data;
};

// Use virtual inheritance
class B : virtual public A {};
class C : virtual public A {};

class D : public B, public C {};

int main() {
    D d;
    d.data = 100; // No ambiguity, there is only one 'data' member in D
    return 0;
}

正文完
 0
评论(没有评论)

YanQS's Blog