《C++面向物件程式設計》課程筆記 lessen5
1. 繼承和派生的概念
1 繼承的概念
在定義一個新的類 B 時,如果該類與某個已有的類 A 相似(指的是 B 擁有 A 的全部特點),那麼就可以把 A 作為一個基類,而把 B 作為基類的一個派生類(也稱子類)。
- 派生類是通過對基類進行修改和擴充得到的。在派生類中,可以擴充新的成員變數和成員函式。修改指的是派生類中寫名稱與基類相同的成員函式,而該函式所進行的行為不太相同。
- 派生類一經定義後,可以獨立使用,不依賴於基類。
- 派生類擁有基類的全部成員函式和成員變數,不論是 private、protected、public。
- 雖然繼承了基類的 private 成員變數,但在派生類新定義的各個成員函式中,不能訪問基類中的 private 成員。
2 派生類的寫法
class 派生類類名:public 基類類名
{
...
};
- 派生類物件的體積,等於基類物件的體積,再加上派生類物件自己的成員變數的體積。在派生類物件中,包含著基類物件,而且基類物件的儲存位置位於派生類物件新增的成員變數之前。
3 繼承例項程式:學籍管理
#include <iostream> #include <string> using namespace std; class CStudent { private: string name; //姓名 string id; //學號 char gender; //性別,‘F’代表女,‘M’代表男 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 CUndergraduate:public CStudent {//本科生類,繼承了CStudent 類 private: string department; //學生所屬的系的名稱 public: void QualifiedForBaoyan() //給予保研資格 { cout<<"qualified for baoyan"<<endl; } void PrintInfo() //對基類 PrintInfo()函式的覆蓋,即為對基類 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() { CUndergraduate s2; s2.SetInfo("Harry Potter","118829212",19,'M',"Computer Science"); cout<<s2.GetName()<<" "; s2.QualifiedForBaoyan(); s2.PrintInfo(); system("pause"); return 0; }
2. 繼承關係和複合關係
1 C++類之間的關係
- 類之間有三種關係:沒有關係、繼承關係、複合關係
- 繼承:“是”關係。基類A,B是基類A的派生類。邏輯上要求:“一個B物件也是一個A物件”。
- 複合:“有”關係。類C中“有”成員變數 k,k 是類D的物件,則C和D是複合關係。邏輯上要求:“D物件是C物件的固有屬性或組成部分”。
複合關係使用的例子 :
class CPoint { double x,y; friend class CCircle; //便於 CCircle 類操作其圓心,因為 CPoint 類的成員物件是其私有的 }; class CCircle { double r; CPoint center; };
小區養狗的管理程式:兩個類:主人類、狗類。狗只有一個主人,而每個業主最多可擁有10條狗。
class CDog;
class CMaster
{
CDog dogs[10];
};
class CDog
{
CMaster m;
};
上面這種處理方法是錯誤的,迴圈定義。CDog 類和CMaster 類的位元組大小是多少?
另一種寫法:為“狗”類設一個“業主”類的成員變數,為“業主”類設一個“狗”類的物件指標陣列。
class CDog;
class CMaster
{
CDog * dogs[10];
};
class CDog
{
CMaster m;
};
該種寫法避免了迴圈定義,但還是錯誤的。無法維護不同的狗中主人資訊一致性的問題。如果改變了一條狗中的主人資訊,另一條狗中的主人資訊也應進行相應的改變。而這種寫法無法做到。
第三種寫法,湊合的寫法。 為“狗”類設一個“業主”類的物件指標,為“業主”類設一個“狗”類的物件陣列。
class CMaster; //CMaster必須提前宣告,不能先寫CMaster類,後寫CDog類
class CDog
{
CMaster * m;
};
class CMaster
{
CDog dogs[10];
};
這種寫法有兩點不好的地方:一、狗不是主人的一部分,狗不是主人的固有屬性。二、“狗”類失去了自由。對“狗”物件進行操作時要通過“主人”物件。
正確的寫法: 為“狗”類設一個“業主”類的物件指標,為“業主”類設一個“狗”類的物件指標陣列。
class CMaster; //CMaster必須提前宣告,不能先寫CMaster類,後寫CDog類
class CDog
{
CMaster * m;
};
class CMaster
{
CDog * dogs[10];
};
3. 覆蓋和保護成員
1 覆蓋的概念
派生類可以定義一個和基類成員同名的成員,這叫覆蓋。在派生類中訪問這類成員時,預設的情況是訪問派生類中定義的成員。要在派生類中訪問由基類定義的同名成員時,要使用作用域符號 :: 。
基類和派生類不要定義同名的成員變數。
2 保護成員
基類的 private 成員可以被下列函式訪問:
- 基類的成員函式
- 基類的友元函式
基類的 public 成員可以被下列函式訪問:
- 基類的成員函式
- 基類的友元函式
- 派生類的成員函式
- 派生類的友元函式
- 其它的函式
基類的 protected 成員可以被下列函式訪問:
- 基類的成員函式
- 基類的友元函式
- 派生類的成員函式可以訪問當前物件的基類的保護成員。
#include <iostream>
using namespace std;
class Father
{
private:
int nPrivate; //私有成員
public:
int nPublic; //公有成員
protected:
int nProtected; //保護成員
};
class Son:public Father
{
void AccessFather()
{
nPublic = 1; //ok
// nPrivate = 1; //error
nProtected = 1; //ok ,訪問當前物件從基類繼承的 protected 成員。
Son f;
// f.nProtected = 1; //error。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
system("pause");
return 0;
}
4. 派生類的建構函式
在建立派生類的物件時,需要呼叫基類的建構函式:初始化派生類物件中從基類繼承的成員。在執行一個派生類的建構函式之前,總是先執行基類的建構函式。
呼叫基類建構函式的兩種方式:
- 顯式方式:在派生類的建構函式中,為基類的建構函式提供引數。derived::derived(arg_derived-list):base(arg_base-list)
- 隱式方式:在派生類的建構函式中,省略基類建構函式時,派生類的建構函式則自動呼叫基類的預設建構函式。(若基類沒有無參建構函式,則編譯出錯)
派生類的解構函式被執行時,執行完派生類的解構函式後,自動呼叫基類的解構函式。
封閉派生類物件(含有成員物件)的建構函式執行順序:
- 先執行基類的建構函式,用以初始化派生類物件 中從基類繼承的成員。
- 再執行成員物件類的建構函式,用以初始化派生類物件中成員物件。
- 最後執行派生類自己的建構函式。
在封閉派生類物件消亡時:
- 先執行派生類自己的解構函式。
- 再依次執行各成員物件類的解構函式。
- 最後執行基類的解構函式。
#include <iostream>
using namespace std;
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) //派生類的初始化列表裡直接初始化基類的 Bug 物件
{
nWings = wings;
}
int main()
{
FlyBug fb(2,3,4);
fb.PrintBug();
fb.nType = 1;
// fb.nLegs = 2; //error.nLegs是基類的私有成員變數,派生類不能訪問
system("pause");
return 0;
}
5. public 繼承的賦值相容規則
class base { };
class derived:public base { };
base b;
derived d;
- 派生類的物件可以賦值給基類物件。b = d 。而基類物件不能賦值給派生類物件。
- 派生類物件可以初始化基類引用。 base & br = d 。
- 派生類物件的地址可以賦值給基類的指標。 base * pb = &d 。
如果派生方式是 private 或 protected ,則上述三條不可行。
2 直接基類和間接基類
在宣告派生類時,只需要列出它的直接基類。
派生類沿著類的層次自動向上繼承它的間接基類。
派生類的成員包括:
- 派生類自己定義的成員
- 直接基類中的所有成員
- 所有間接基類的全部成員