Bootstrap

C+学习18、基类和派生类

在C++的面向对象编程(OOP)中,基类和派生类(或称为父类和子类)是继承关系的核心概念。继承允许我们基于已有的类创建新的类,从而实现代码复用和扩展。本文将深入探讨C++中基类和派生类的关系,包括它们的定义、语法、访问控制、构造函数与析构函数的调用顺序、多重继承、虚函数与多态、以及实战应用。

一、基类和派生类的定义与语法

在C++中,基类(Base Class)是一个已经定义好的类,它包含了一些基本的属性和方法。派生类(Derived Class)则是从基类继承而来的新类,它继承了基类的所有公有和保护成员(除非在派生类中被覆盖或隐藏),并可以添加新的属性和方法。

// 定义基类
class Base {
public:
    Base() { std::cout << "Base Constructor" << std::endl; }
    virtual ~Base() { std::cout << "Base Destructor" << std::endl; }
    
    void display() { std::cout << "Display function in Base" << std::endl; }
};
 
// 定义派生类
class Derived : public Base {
public:
    Derived() { std::cout << "Derived Constructor" << std::endl; }
    ~Derived() { std::cout << "Derived Destructor" << std::endl; }
    
    // 覆盖基类的display函数
    void display() { std::cout << "Display function in Derived" << std::endl; }
    
    void newFunction() { std::cout << "New function in Derived" << std::endl; }

};

在上面的例子中,Base是一个基类,它有一个构造函数、一个析构函数和一个display函数。Derived是一个从Base继承而来的派生类,它覆盖了基类的display函数,并添加了一个新的newFunction函数。

二、访问控制与继承类型

C++中的继承可以是公有的(public)、保护的(protected)或私有的(private),这决定了基类成员在派生类中的访问级别。

  • 公有继承:基类的公有成员和保护成员在派生类中仍然是公有成员和保护成员;基类的私有成员在派生类中仍然是私有的(但不可直接访问)。
  • 保护继承:基类的公有成员和保护成员在派生类中变为保护成员;基类的私有成员仍然是私有的。
  • 私有继承:基类的公有成员和保护成员在派生类中变为私有成员;基类的私有成员仍然是私有的。
class PublicDerived : public Base { /* ... */ };
class ProtectedDerived : protected Base { /* ... */ };

class PrivateDerived : private Base { /* ... */ };

在大多数情况下,公有继承是最常用的,因为它保持了基类接口的公共可见性。

三、构造函数与析构函数的调用顺序

当创建派生类对象时,基类的构造函数会首先被调用(按照它们在类定义中的顺序,如果有多个基类的话),然后是派生类的构造函数。当派生类对象被销毁时,析构函数的调用顺序与构造函数的调用顺序相反,即先调用派生类的析构函数,然后调用基类的析构函数。

Derived d; // 调用顺序:Base() -> Derived()

// d超出作用域或显式销毁时:~Derived() -> ~Base()

四、多重继承

C++允许一个类从多个基类继承,这称为多重继承。然而,多重继承可能会引入复杂性和潜在的问题,如菱形继承(或称为钻石继承)中的二义性问题。为了解决这些问题,C++引入了虚继承(Virtual Inheritance)。

class Base1 { /* ... */ };
class Base2 { /* ... */ };

class Derived : public Base1, public Base2 { /* ... */ };

在菱形继承中,如果Base1和Base2都有一个共同的基类(比如CommonBase),并且Derived从Base1和Base2继承,那么Derived中就会有两个CommonBase的实例,导致二义性。虚继承可以解决这个问题,它确保无论通过哪条路径,Derived中都只有一个CommonBase的实例。

class CommonBase { /* ... */ };
class Base1 : virtual public CommonBase { /* ... */ };
class Base2 : virtual public CommonBase { /* ... */ };

class Derived : public Base1, public Base2 { /* ... */ };

五、虚函数与多态

在C++中,多态性允许我们通过基类指针或引用来调用派生类中的函数。为了实现多态性,我们需要使用虚函数。虚函数是在基类中声明的,并在派生类中被覆盖的函数。当通过基类指针或引用来调用虚函数时,会根据对象的实际类型来调用相应的函数版本。

class Base {
public:
    virtual void display() { std::cout << "Display in Base" << std::endl; }
    virtual ~Base() = default; // 虚析构函数确保正确调用派生类的析构函数
};
 
class Derived : public Base {
public:
    void display() override { std::cout << "Display in Derived" << std::endl; }
};
 
Base* b = new Derived();
b->display(); // 输出:Display in Derived

delete b; // 调用Derived的析构函数,然后是Base的析构函数

六、实战应用:基类和派生类的综合示例

以下是一个综合示例,展示了基类和派生类在C++中的实际应用。

#include <iostream>
#include <vector>
#include <memory>
 
// 定义基类:Shape
class Shape {
public:
    virtual void draw() const = 0; // 纯虚函数,使Shape成为抽象基类
    virtual ~Shape() = default;
};
 
// 定义派生类:Circle
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    void draw() const override { std::cout << "Drawing a Circle with radius " << radius << std::endl; }
};
 
// 定义派生类:Rectangle
class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    void draw() const override { std::cout << "Drawing a Rectangle with width " << width << " and height " << height << std::endl; }
};
 
int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    
    shapes.push_back(std::make_unique<Circle>(5.0));
    shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));
    
    for (const auto& shape : shapes) {
        shape->draw(); // 通过基类指针调用派生类的draw函数,展示多态性
    }
    
    return 0;

}

在这个示例中,我们定义了一个抽象基类Shape,它有一个纯虚函数draw。Circle和Rectangle是从Shape继承而来的派生类,它们各自实现了draw函数。在main函数中,我们使用std::vector和std::unique_ptr来管理Shape对象的生命周期,并通过基类指针调用派生类的draw函数,展示了多态性的应用。

;