1. 程式人生 > >《C++面向物件程式設計》課程筆記 lessen5

《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 直接基類和間接基類

在宣告派生類時,只需要列出它的直接基類。

 派生類沿著類的層次自動向上繼承它的間接基類。

派生類的成員包括:

  • 派生類自己定義的成員
  • 直接基類中的所有成員
  • 所有間接基類的全部成員