跳转到内容

C++ 编程/类/继承

来自 Wikibooks,开放世界中的开放书籍

继承(派生)

[编辑 | 编辑源代码]

正如在介绍编程范式时看到的,继承 是一个描述对象类型或类之间关系的属性。它是 OOP 的一个特性,在 C++ 中,类共享此属性。

派生是使用继承属性创建新类的行为。可以使用另一种类甚至多种类派生一个类(多重继承),就像一棵树,我们可以将基类称为根,并将子类称为任何叶子;在任何其他情况下,父/子关系将存在于从另一个类派生的每个类。

基类

基类是指创建的类,其目的是从中派生其他类。

子类

子类是指从另一个类派生的类,该类现在将成为该子类的父类。

父类

父类是我们用来创建我们作为子类引用的类的最接近的类。

例如,假设您正在创建一个游戏,使用不同的汽车,并且您需要特定类型的汽车供警察使用,以及另一种类型的汽车供玩家使用。这两种类型的汽车共享类似的属性。主要区别(在此示例情况下)是警察类型的汽车顶部将有警笛,而玩家的汽车则没有。

准备警察和玩家的汽车的一种方法是为警察的汽车和玩家的汽车创建单独的类,如下所示

class PlayerCar {
   private:
     int color;
  
   public:
     void driveAtFullSpeed(int mph){
       // code for moving the car ahead
     }
};

class PoliceCar {
private:
  int color;
  bool sirenOn;  // identifies whether the siren is on or not
  bool inAction; // identifies whether the police is in action (following the player) or not
  
public:
  bool isInAction(){
    return this->inAction;
  }

  void driveAtFullSpeed(int mph){
    // code for moving the car ahead
  }
  
};

然后为这两辆车创建单独的对象,如下所示

PlayerCar player1;
PoliceCar policemen1;

因此,除了您可以轻松注意到的一个细节之外:上面两个类中某些代码部分非常相似(如果不是完全相同)。本质上,您必须在两个不同的位置输入相同的代码!当您更新代码以包括handBrake()pressHorn() 的方法(函数)时,您必须在上面两个类中都这样做。

因此,为了避免在单个项目中的多个位置编写相同代码的令人沮丧(且令人困惑)的任务,您使用继承。

既然您知道继承在 C++ 中解决什么样的问题,让我们研究如何在程序中实现继承。顾名思义,继承允许我们创建新的类,这些类会自动具有现有类中的所有代码。这意味着,如果有一个名为MyClass 的类,则可以创建一个名为MyNewClass 的新类,该类将具有MyClass 类中存在的所有代码。以下代码段显示了所有这些

class MyClass {
  protected:
         int age;
  public:
         void sayAge(){
             this->age = 20;
             cout << age;
         }
};

class MyNewClass : public MyClass {

};

int main() {
  
  MyNewClass *a = new MyNewClass();
  a->sayAge();
  
  return 0;
  
}

如您所见,使用冒号“:”我们可以从现有类中继承一个新类。就这么简单!MyClass 类中的所有代码现在都可用于MyNewClass 类。如果您足够聪明,您已经可以看出它提供的优势。如果您像我一样(即不太聪明),您可以查看以下代码段来了解我的意思

class Car {
  protected:
         int color;
         int currentSpeed;
         int maxSpeed;
  public:
         void applyHandBrake(){
             this->currentSpeed = 0;
         }
         void pressHorn(){
             cout << "Teeeeeeeeeeeeent"; // funny noise for a horn
         }
         void driveAtFullSpeed(int mph){
              // code for moving the car ahead;
         }
};

class PlayerCar : public Car {

};

class PoliceCar : public Car {
  private:
         bool sirenOn;  // identifies whether the siren is on or not
         bool inAction; // identifies whether the police is in action (following the player) or not
  public:
         bool isInAction(){
             return this->inAction;
         }
};

在上面的代码中,新创建的两个类PlayerCarPoliceCar 是从Car 类继承的。因此,Car 类中的所有方法和属性(变量)都可用于为玩家的汽车和警察的汽车创建的新类。从技术上讲,在 C++ 中,这种情况下的Car 类是我们的“基类”,因为这是其他两个类所基于(或继承)的类。

这里需要注意的一点是关键字protected 而不是通常的private 关键字。这不是什么大问题:当我们想要确保我们在基类中定义的变量应该在从该基类继承的类中可用时,我们会使用protected。如果在Car 类的类定义中使用private,您将无法在继承的类中继承这些变量。

类继承有三种类型:公有、私有和保护。我们使用关键字public 来实现公有继承。从基类以关键字 public 继承的类,将所有公有成员继承为公有成员,受保护的数据继承为受保护的数据,私有数据继承但不能直接访问。

以下示例显示了从基类 Form “公有”继承的类 Circle

class Form {
private:
  double area;

public:
  int color;

  double getArea(){
    return this->area;
  }

  void setArea(double area){
    this->area = area;
  }

};

class Circle : public Form {
public:
  double getRatio() {
    double a;
    a = getArea();
    return sqrt(a / 2 * 3.14);
  }

  void setRatio(double diameter) {
    setArea( pow(diameter * 0.5, 2) * 3.14 );
  }

  bool isDark() {
    return (color > 10);
  }

};

新类 Circle 继承了基类 Form 中的属性 area(属性 area 隐式地是类 Circle 的属性),但它不能直接访问它。它通过函数 getArea 和 setArea 来做到这一点(这些函数在基类中是公有的,并在派生类中保持公有)。但是,color 属性作为公有属性继承,并且类可以直接访问它。

下表显示了在三种不同类型的继承中属性是如何继承的

基类中的访问说明符
private protected public
私有继承 该成员不可访问。 该成员是私有的。 该成员是私有的。
保护继承 该成员不可访问。 该成员是受保护的。 该成员是受保护的。
公有继承 该成员不可访问。 该成员是受保护的。 该成员是公有的。

如上表所示,受保护成员在公有继承中继承为受保护方法。因此,当我们想要声明一个方法在类外部不可访问并且不想在派生类中失去对其访问权限时,应该使用 protected 标签。但是,有时失去可访问性可能是有用的,因为我们在基类中封装了细节。

让我们想象一下,我们有一个类,它有一个非常复杂的方法“m”,该方法调用在类中声明为私有的许多辅助方法。如果我们从中派生一个类,我们不应该关心这些方法,因为它们在派生类中不可访问。如果另一个程序员负责派生类的设计,允许访问这些方法可能会导致错误和混淆。因此,当我们可以使用 private 标签进行相同的设计时,最好避免使用 protected 标签。

现在还有一个额外的“语法技巧”。如果基/父类有一个需要参数的构造函数,我们就会遇到麻烦,您可能会认为。当然,直接调用构造函数是被禁止的,但是我们有一种特殊的语法来实现此目的。方法是在定义传递的类的构造函数时,像这样调用父构造函数

ChildClass::ChildClass(int a, int b) : ParentClass(a, b)
{
  //Child constructor here
}

注意
避免在父构造函数调用中引用子类的内部,因为没有关于类创建顺序的保证,并且父类仍需初始化。一种解决方法是在父类中创建一个“初始化器”方法,以便对它的任何调用都会提供这些保证。这不是最好的解决方案,通常是设计错误的指示,但有时是必需的。

多重继承

[编辑 | 编辑源代码]

多重继承 允许构建从多个类型或类继承的类。这与单继承形成对比,在单继承中,一个类只从一个类型或类继承。

多重继承可能会导致一些令人困惑的情况,并且比单继承复杂得多,因此关于它的好处是否超过它的风险存在一些争议。多重继承多年来一直是一个敏感问题,反对者指出了它的复杂性和“菱形问题”等情况下的模糊性。大多数现代 OOP 语言不允许多重继承。

声明的派生顺序与确定构造函数的默认初始化顺序和析构函数清理顺序相关。

class One
{
  // class internals
};

class Two
{
  // class internals
};

class MultipleInheritance : public One, public Two
{
  // class internals
};

注意
请记住,当创建将从中派生的类时,析构函数可能需要进一步考虑。

华夏公益教科书