1. 程式人生 > >C++之面向物件程式設計的基本特點(抽象、封裝、繼承、多型)

C++之面向物件程式設計的基本特點(抽象、封裝、繼承、多型)

面向物件程式設計的主要特點:抽象、封裝、繼承、多型。

1、抽象

編寫程式的目的就是描述和解決現實世界中的問題。第一步就是將現實世界中的物件和類如實的反映在程式中。

定義:對具體問題(物件)進行概括,抽出一類物件的公共性質並加以描述的過程。

兩個方面:資料抽象和行為抽象。

例1:加入我們要在計算機上實現一個簡單的時鐘程式,通過對時鐘進行分析可以看出,需要3個整型資料來儲存時間,分別表示時、分、秒——這就是對時鐘所具有的資料進行抽象。另外,時鐘要具有顯示時間、設定時間等基本功能——這就是對它的行為抽象。用C++的變數和函式可以將抽象後的時鐘屬性描述如下:

//資料抽象
int hour, minute, second;
//行為抽象(也叫功能抽象)
ShowTime();
SetTime();
例2:對人進行抽象。
//資料抽象
char *name, *sex, *age;
//行為抽象
Eat();
Walk();
Work();
Study();

2、封裝

定義:將抽象得到的資料和行為相結合,形成一個有機的整體。也就是將資料與操作資料的函式程式碼進行有機的結合,形成“類”。

例:在抽象的基礎上,我們將時鐘的資料和功能封裝起來,構成一個時鐘類:

class Clock{
public:
    void SetTime(int NewH, int NewM, int NewS);
    void ShowTime();
private:
    int Hour, Minute, Seconds;
};
通過封裝,使一部分成員充當類與外部的藉口,而將其它的成員隱藏起來。達到對成員訪問控制權限的合理控制。

將資料和程式碼封裝為一個可重用的程式模組,在編寫程式時就可以有效的利用已有的成果。通過外部介面依據特定的規則就可以使用封裝好的模組,使用時也不必瞭解類的實現細節。

3、繼承

現實世界中,很多事物之間都有著複雜的聯絡。繼承便是其中一種:汽車和自行車都從屬於“交通工具”,但是它們在外觀和功能上都各具不同,各有千秋。同樣,在面向物件的程式設計中,提供了類的繼承機制。大大提高了程式碼的可重用性和可擴充性。

被繼承的類:基類。通過繼承得到的新類:派生類。

派生類繼承了基類的所有資料成員和除建構函式和解構函式之外的函式成員。

同名隱藏:如果派生類聲明瞭一個和基類成員同名的新成員,這時在派生類中或者通過派生類的物件,直接使用成員名就只能訪問到派生類中宣告的同名成員。

繼承方式:public、protected、private

1)公有繼承:基類的公有和保護成員的訪問屬性在派生類中不變,而基類的私有成員在派生類中不可直接訪問。(也就是說基類的公有成員和保護成員在派生類中,訪問屬性不變,仍作為派生類的公有成員和保護成員,派生類的其它成員可以直接訪問它們,在類外只能通過派生類的物件訪問從基類繼承來的公有成員)

#include <iostream>
using namespace std;

class Point{
public:
	void InitP(float xx = 0, float yy = 0) { X = xx, Y = yy; }
	void Move(float xOff, float yOff) { X += xOff; Y += yOff; }
	float GetX() {return X;}
	float GetY() {return Y;}
private:
	float X, Y;
};
class Rectangle : public Point{
public:
	void InitR(float x, float y, float h, float w)
	{ InitP(x, y); W = w; H = h; } //派生類訪問基類公有成員
	float GetH(){return H;}
	float GetW(){return W;}
private:
	float W, H;
};

int main()
{
	Rectangle rect;
	rect.InitR(2, 3, 20, 10);
	rect.Move(3, 2);

	cout << rect.GetX() << " "
		 << rect.GetY() << " "
		 << rect.GetW() << " "
		 << rect.GetH() << endl;

	return 0;
}
2)私有繼承:基類的公有和保護成員都以私有成員身份出現在派生類中,而基類的私有成員在派生類中不可直接訪問。(派生類的其它成員可以直接訪問它們,但是在類外部通過派生類的物件無法直接訪問它們)
#include <iostream>
using namespace std;

class Point{
public:
	void InitP(float xx = 0, float yy = 0) { X = xx, Y = yy; }
	void Move(float xOff, float yOff) { X += xOff; Y += yOff; }
	float GetX() {return X;}
	float GetY() {return Y;}
private:
	float X, Y;
};
class Rectangle : private Point{
public:
	void InitR(float x, float y, float h, float w)
	{ InitP(x, y); W = w; H = h; } //派生類訪問基類公有成員
	void Move(float xOff, float yOff){ Point :: Move(xOff, yOff); }
	float GetX(){return Point :: GetX();}
	float GetY(){return Point :: GetY();}
	float GetH(){return H;}
	float GetW(){return W;}
private:
	float W, H;
};

int main()
{
	Rectangle rect;
	//rect.InitP(2, 3);//錯誤,私有繼承,派生類物件不能訪問基類成員
	rect.InitR(2, 3, 20, 10);
	rect.Move(3, 2);

	cout << rect.GetX() << " "
		 << rect.GetY() << " "
		 << rect.GetW() << " "
		 << rect.GetH() << endl;

	return 0;
}

3)保護繼承:基類的公有和保護成員都以保護成員身份出現在派生類中,而基類的私有成員不可直接訪問。這樣,派生類的其他成員就可以直接訪問從基類繼承來的公有和保護成員,但在類外部通過派生類的物件無法直接訪問它們。

所以在派生類中,成員的訪問屬性可以分為四類:

  • 不可訪問成員:從基類私有成員繼承而來,派生類內部成員或者派生類物件均無法訪問
  • 私有成員
  • 保護成員
  • 公有成員

型別相容性原則:在需要基類物件的任何地方,都可以使用公有派生類的物件來替代。通過公有繼承,派生類得到了基類中除建構函式、解構函式之外的所有成員。這樣,凡是基類能解決的問題,公有派生類也可以解決。型別相容規則所指的替代包括以下情況:

  • 派生類的物件可以賦值給基類物件
  • 派生類的物件可以賦值給基類引用
  • 派生類物件的地址可以賦值給基類指標
替代之後,派生類的物件就可以作為基類物件使用,但只能使用從基類繼承的成員。 例如下例中,
#include <iostream>
using namespace std;

class B0{
public:
	void dispaly() { cout << "B0 :: dispaly()" << endl; } //
};

class B1 : public B0{//公有派生類B1
public:
	void dispaly() { cout << "B1 :: dispaly()" << endl; }
};

class D1 : public B1{//公有派生類D1
public:
	void dispaly() { cout << "B2 :: dispaly()" << endl; }
};

void fun( B0 *ptr){
	ptr -> dispaly();
}
int main()
{
	B0 b0;
	B1 b1;
	D1 d1;
	B0 *p;
	p = &b0;
	fun(p);
	p = &b1;
	fun(p);
	p = &d1;
	fun(p);

	return 0;
}
執行結果為
4、多型

定義:同樣的訊息被不同型別的物件接收時導致不同的行為。所謂訊息是指對類的成員函式的呼叫,不同的行為是指不同的實現,也就是呼叫了不同的成員函式。

多型從實現的角度,可以分為編譯時多型執行時多型

編譯時多型在編譯的過程中確定同名操作的具體操作物件,執行時多型在執行過程中才動態地確定操作所針對的具體物件。這種確定操作的具體物件的過程就叫做繫結(也叫聯編)。用面向物件的術語講,就是把一條訊息和一個物件的方法相結合的過程。

繫結工作在編譯連結階段完成的叫做靜態繫結。在程式執行階段完成的情況叫做動態繫結。

在講繼承時,我們有提到型別相容性原則,也就是在派生類的物件可以作為基類使用,但是隻能使用從基類繼承來的成員。如果我們需要通過基類的指標指向派生類的物件,並訪問某個與基類同名的函式,那麼就要在基類中將這個同名函式說明為虛擬函式。

例如:上一個例子中,我們只需要把基類的函式設定為虛擬函式,結果就不一樣了,

#include <iostream>
using namespace std;

class B0{
public:
	virtual void dispaly() { cout << "B0 :: dispaly()" << endl; } //設定為虛擬函式
};

class B1 : public B0{//公有派生類B1
public:
	void dispaly() { cout << "B1 :: dispaly()" << endl; }
};

class D1 : public B1{//公有派生類D1
public:
	void dispaly() { cout << "B2 :: dispaly()" << endl; }
};

void fun( B0 *ptr){
	ptr -> dispaly();
}
int main()
{
	B0 b0;
	B1 b1;
	D1 d1;
	B0 *p;
	p = &b0;
	fun(p);
	p = &b1;
	fun(p);
	p = &d1;
	fun(p);

	return 0;
}
結果:



這篇文章是在看《C++語言程式設計》時總結的。

以前還總結過一篇關於封裝、繼承、多型:C++之封裝、繼承、多型,有興趣可以看看,讓自己掌握更熟練一些。