《程序設計實習》之【繼承和派生】

繼承和派生的概念

繼承:在定義一個新的類B時,如果該類與某個已有的類A相似(指的是B擁有A的全部特點), 那麼就可以把A作爲一個基類,而把B作爲基類的一個派生類(也稱子類)。

  • 派生類是通過對基類進行修改和擴充得到的。在派生類中,可以擴充新的成員變量 和成員函數。
  • 派生類一經定義後,可以獨立使用,不依賴於基類。
  • 派生類擁有基類的全部成員函數和成員變 量,不論是private、protected、public
    • 在派生類的各個成員函數中,不能訪問基類中的private成員。

需要繼承機制的例子

  • 所有學生都有的共同屬性:
    • 姓名
    • 學號
    • 性別
    • 成績
  • 所有的學生都有的共同方法(成員函數):
  • 是否該留級
  • 是否該獎勵
  • 而不同的學生,又有各自不同的屬性和方法
    • 研究生
      • 導師
    • 大學生
    • 中學生
      • 競賽特長加分
  • 如果爲每類學生都從頭編寫一個類,顯然會有不少重複的代碼,浪費。
  • 比較好的做法是編寫一個“學生”類,概括了各種學生的共同特點,然後從“學生”類派 生出“大學生”類,“中學生”類,“研究生類”。

派生類的寫法

class 派生類名 : public 基類名 {

};
class CStudent { 
private:
    string sName; 
    int nAge;
public:
    bool IsThreeGood() { }; 
    void SetName( const string & name ) { sName = name; }
};
class CUndergraduateStudent : public CStudent {
private:
    int nDepartment;
public:
    bool IsThreeGood() { ...... };  //覆蓋 
    bool CanBaoYan() { .... }; 
};   // 派生類的寫法是:類名: public 基類名
class CGraduatedStudent : public CStudent {
private:
    int nDepartment;
    char szMentorName[20];
public:
    int CountSalary() { ... };
};

派生類對象的內存空間

派生類對象的體積,等於基類對象的體積,再加上派生類對象自己的成員變量的體積。在派生類對象中,包含着基類對象,而且基類對象的存儲位置位於派生類對象新增的成員變量之前

繼承實例程序:學籍管理

#include <iostream> 
#include <string> 
using namespace std;
class CStudent {
private:
    string name; 
    string id; //學號 
    int age; 
public:
    void PrintInfo(); 
    void SetInfo( const string & name_,const string & id_, int age_, char gender_ ); 
    string GetName() { return name; }
};

void CStudent::PrintInfo() { 
    cout << "Name:" << name << endl; 
    cout << "ID:" << id << endl; 
    cout << "Age:" << age << endl; 
    cout << "Gender:" << gender << endl; 
}

void CStudent::SetInfo( const string & name_,const string & id_, int age_,char gender_ ) { 
    name = name_; 
    id = id_; 
    age = age_; 
    gender = gender_; 
}
class CUndergraduateStudent:public CStudent { //本科生類,繼承了CStudent類
private:
    string department; //學生所屬的系的名稱
public:
    void QualifiedForBaoyan() {  //給予保研資格
        cout << “qualified for baoyan” << endl;
    }

    void PrintInfo() {
        CStudent::PrintInfo(); //調用基類的PrintInfo
        cout << “Department:” << department <<endl;
    }

    void SetInfo( const string & name_,const string & id_,
int age_,char gender_ ,const string & department_) {
        CStudent::SetInfo(name_,id_,age_,gender_); //調用基類的SetInfo
        department = department_;
    }
};
int main() {
    CUndergraduateStudent s2;
    s2.SetInfo(“Harry Potter ”, “118829212”,19,‘M’,“Computer Science”); 
    cout << s2.GetName() << “ ” ; 
    s2.QualifiedForBaoyan (); 
    s2.PrintInfo (); 
    return 0;
}

輸出結果:

Harry Potter qualified for baoyan
Name:Harry Potter
ID:118829212
Age:19
Gender:M
Department:Computer Science

繼承關係和複合關係

類之間的關係:

  • 沒關係
  • 繼承:“”關係
    • 基類A,B是基類A的派生類
    • 邏輯上要求:“一個B對象也一個A對象”。
  • 複合:“”關係
    • 類C中“”成員變量k,k是類D的對象,則C和D是複合關係
    • 一般邏輯上要求:“D對象是C對象的固有屬性或組成部分”

複合關係的使用

幾何形體程序中,需要寫“點”類,也需要寫“圓”類,兩者的關係就是複合關係 —- 每一個“圓”對象裏都包含 (有)一個“點”對象,這個“點”對象就是圓心。

如果要寫一個小區養狗管理程序,需要寫一個“業主”類,還需要寫一個“狗”類。
而狗是有 “主人” 的,主人當然是業主(假定狗只有一個主人,但一個業主可以有最多10條狗)。

上面的寫法有循環定義的錯誤,無法確定兩個類所佔內存空間爲多大。

上面的寫法無法保證多個狗共同主人的數據統一問題。

此類寫法如果想調用某隻狗,只能通過其主人來進行,狗沒有自由。

基類/派生類同名成員與protected關鍵字

基類和派生類有同名成員的情況

void derived::access() {
    j = 5;  //error基類的私有成員
    i = 5;  //引用的是派生類的 i
    base::i = 5; //引用的是基類的 i
    func(); //派生類的
    base::func(); //基類的
}

derived obj;
obj.i = 1;
obj.base::i = 1;

訪問範圍說明符

  • 基類的private成員,可以被下列函數訪問:
    • 基類的成員函數
    • 基類的友元函數
  • 基類的public成員,可以被下列函數訪問:
    • 基類的成員函數
    • 基類的友元函數
    • 派生類的成員函數
    • 派生類的友元函數
    • 其他的函數
  • 基類的protected成員,可以被下列函數訪問:
    • 基類的成員函數
    • 基類的友元函數
    • 派生類的成員函數可以訪問當前對象的基類的保護成員

保護成員

class Father {
private: 
    int nPrivate;  //私有成員
public: 
    int nPublic;   //公有成員
protected: 
    int nProtected; // 保護成員
};

class Son : public Father {
    void AccessFather () {
        nPublic = 1; // ok;
        nPrivate = 1; // wrong
        nProtected = 1;  // OK, 訪問從基類繼承的protected成員
        Son f;
        f.nProtected = 1; //wrong, f不是當前對象
    }
};
int main() {
    Father f;
    Son s;
    f.nPublic = 1;     // Ok
    s.nPublic = 1;     // Ok
    f.nProtected = 1;  // error
    f.nPrivate = 1;    // error
    s.nProtected = 1;  // error
    s.nPrivate = 1;    // error
    return 0;
}

派生類的構造函數

派生類的構造函數

  • 派生類對象包含基類對象
  • 執行派生類構造函數之前,先執行基類的構造函數
  • 派生類交代基類初始化,具體形式:
構造函數名(形參表):基類名(基類構造函數實參表) {
}
class Bug {
private :
    int nLegs;
    int nColor;
public:
    int nType;
    Bug (int legs, int color);
    void PrintBug () { };
};
Bug::Bug( int legs, int color) {
    nLegs = legs;
    nColor = color;
}
class FlyBug: public Bug {  // FlyBug是Bug的派生類
    int nWings; 
public:
    FlyBug(int legs, int color, int wings);
};

//錯誤的FlyBug構造函數:
FlyBug::FlyBug (int legs, int color, int wings) {
    nLegs = legs;   // 不能訪問
    nColor = color; // 不能訪問
    nType = 1;    // ok
    nWings = wings;
}

//正確的FlyBug構造函數:

FlyBug::FlyBug (int legs, int color, int wings):Bug(legs, color) {
    nWings = wings;
}
int main() {
    FlyBug fb ( 2,3,4);
    fb.PrintBug();
    fb.nType = 1;
    fb.nLegs = 2 ; // error.nLegs is private
    return 0;
}
  • 在創建派生類的對象
    • 需要調用基類的構造函數,初始化派生類對象中從基類繼承的成員
    • 在執行一個派生類的構造函數之前,總是先執行基類的構造函數
  • 調用基類構造函數的兩種方式
    • 顯示方式:派生類的構造函數中 -> 基類的構造函數提供參數
      • derived::derived(arg_derived-list):base(arg_base-list)
    • 隱式方式:
      • 派生類的構造函數中,省略基類構造函數時,派生類的構造函數,自動調用基類的默認構造函數
  • 派生類的析構函數被執行時,執行完派生類的析構函數後,自動調用基類的析構函數
class Base {
public:
    int n;
    Base(int i) : n(i) {
        cout << "Base " << n << " constructed" << endl;
    }
    ~Base() {
        cout << "Base " << n << " destructed" << endl;
    }
};

class Derived : public Base {
public:
    Derived(int i) : Base(i) {
        cout << "Derived constructed" << endl;
    }
    ~Derived() {
        cout << "Derived destructed" << endl;
    }
};

int main() {
    Derived Obj(3); 
    return 0;
}

輸出結果:

Base 3 constructed
Derived constructed
Derived destructed
Base 3 destructed

包含成員對象的派生類的構造函數

class Skill {
public:
    Skill(int n) { }
};

class FlyBug: public Bug {
    int nWings;
    Skill sk1, sk2;
public:
    FlyBug(int legs, int color, int wings);
};

FlyBug::FlyBug( int legs, int color, int wings): Bug(legs, color), sk1(5), sk2(color) { 
    nWings = wings;
}
  • 創建派生類的對象 時, 執行派生類的構造函數之前:
    • 調用基類的構造函數 -> 初始化派生類對象中從積累繼承的成員
    • 調用成員對象類的構造函數 -> 初始化派生對象中的成員對象
  • 執行完派生類的析構函數
    • 調用成員對象類的析構函數
    • 調用基類的析構函數
  • 析構函數的調用順序與構造函數的調用順序相反

public繼承的賦值兼容規則

class base { }; 
class derived : public base { }; 
base b; 
derived d;
  • 派生類的對象可以賦值給基類對象
b = d;
  • 派生類對象可以初始化基類引用
base & br = d;
  • 派生類對象的地址可以賦值給基類指針
base * pb = & d;

如果派生方式是 privateprotected,則上述三條不可行。

直接基類和間接基類


類A派生類B,類B派生類C,類C派生類D,……

  • 類A是類B的直接基類
  • 類B是類C的直接基類,類A是類C的間接基類
  • 類C是類D的直接基類,類A、B是類D的間接基類

在聲明派生類時,只需要列出它的直接基類,派生類沿着類的層次自動向上繼承它的間接基類。

派生類的成員包括:

  • 派生類自己定義的成員
  • 直接基類中的所有成員
  • 所有間接基類的全部成員
#include <iostream> 
using namespace std; 
class Base {
public:
    int n; 
    Base(int i):n(i) { 
        cout << "Base " << n << " constructed" << endl; 
    } 
    ~Base() { 
        cout << "Base " << n << " destructed" << endl; 
    }
};

class Derived : public Base {
public:
    Derived(int i):Base(i) {
        cout << "Derived constructed" << endl;
    }
    ~Derived() {
        cout << "Derived destructed" << endl;
    }
};
class MoreDerived : public Derived {
public:
    MoreDerived():Derived(4) {
        cout << "More Derived constructed" << endl;
    }
    ~MoreDerived() {
        cout << "More Derived destructed" << endl;
    }
};
int main() {
    MoreDerived Obj;
    return 0;
}

輸出結果:

Base 4 constructed 
Derived constructed 
More Derived constructed 
More Derived destructed 
Derived destructed 
Base 4 destructed
發佈了97 篇原創文章 · 獲贊 27 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章