跳轉到內容

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
};

注意
請記住,當建立將從中派生的類時,解構函式可能需要進一步考慮。

華夏公益教科書