1. 程式人生 > >C++的public繼承中的public、private和protected

C++的public繼承中的public、private和protected

    C++語言是對C語言的一種增強,而其主要貢獻在於,為C語言增加了類和模板等功能,可以幫助實現面對物件程式設計和程式碼複用等更方便的功能。

    在C++語言的類,一般都包含兩種成員,分別是成員變數和成員函式。成員變數可以用來表示該類的某些狀態,而成員函式則可以用來對這些成員變數進行操作。而對於這些成員而言,最核心的概念當屬類的封裝和繼承的概念。

1. 封裝

    所謂封裝,就是編寫一個類物件,只留出使用者介面,不需要使用者去關心裡面的東西是如何構造的,拿來就可以直接使用。這就像一臺手機,賣家賣給使用者,使用者只需要使用手機而不需要知道手機是怎樣製造的,同時也不可能拆開看個究竟再自己仿造一個,可以說是一種“黑箱概念”。這既方便了使用者的使用,讓使用者不再掛念內部實現的細節,同時也避免了使用者有意地(比如想仿製一個相同的,可能導致智慧財產權侵權)或無意地(可能導致類的損壞而影響使用)對原有的封裝進行修改。

    可以通過一個生活中的簡單的例子來理解C++實現封裝的方法。手機廠商不允許使用者擅自篡改手機系統,但手機廠商必須允許使用者對手機在可控的範圍內進行操作,比如增加或刪除一個軟體或電話號碼。而這樣的增加、刪除或修改資料一定是不可以篡改系統的。若要實現這樣的效果,唯一的辦法就是廠商自己來制定一套手機操作的規則,讓使用者只能使用他提供的規則去操作,這樣就避免了所有的對原封裝的篡改。與此原理相似地,為了實現封裝,C++提供了在類中的兩種操作範圍(這個用詞可能不太妥當):public和private,中文意思可以稱為“公有”和“私有”。public的成員函式都是可以通過類物件來直接訪問的,可以通過"class_name.class_method(para1,para2)"的語法格式或者使用類指標以"p_class_name->method(para1.para2)"的形式來實現的。如果將成員變數放在public裡邊,同樣可以利用類似的方法訪問,即"class_name.class_member"或"p_class_name->class_member"。但根據C++對類封裝的原則,不希望提供給使用者隨意修改修改類內成員變數的許可權,因此,C++語言為類提供了private,就是希望類的編寫者把該類所涉及的所有資料都放進這個部分,然後在public中編寫一些使用者介面(公有成員函式),讓使用者只能通過這些介面來進行操作。

2.繼承

    我之前寫過的一篇講“派生類的重新定義”的文章簡要地介紹過“繼承”的概念。由於學習進度,我在這裡主要想講一下protected關鍵字在public繼承中的效果(其他型別的繼承我還沒有學到,後面會繼續補充相關的內容)。

    為了更加簡明易懂,這裡舉一個簡單的例子來進行說明。

    首先定義一個基類“shape”,它包含"private"、"protected"和"public"型別的成員變數和成員函式,並在main函式中建立一個“shape”類的物件s1並呼叫成員函式來演示各種型別成員之間的訪問許可權。

    之後定義一個從基類“shape”派生出來的“rectangle”類,它在基類的基礎上,又增加了自己獨有的一些public和private成員變數和成員函式,並試圖讓這些成員函式去訪問其基類中自帶的"private"、"protected"和"public"型別的成員變數和成員函式。之後再在main函式中建立一個“rectangle”物件r1,再嘗試用它可以呼叫哪些成員函式和訪問哪些成員變數。

    下面是測試程式碼。由於時間關係,這裡的程式碼並沒有呼叫<iostream>庫檔案的控制檯顯示的功能,只是通過編譯器Visual Studio2017的編譯來測試對類內各種成員是否可以訪問(直接或間接)。有興趣的話可以在裡面的各個成員函式中新增"cout << "來輔助觀察效果。

//本程式可直接貼上在.cpp檔案中執行,
//僅用於瞭解private和public在類繼承中如何對類成員賦予許可權,
//編譯可以通過即可,執行也不會在螢幕上顯示任何資訊。
//主要的講解都在註釋當中。
#include <iostream>
using namespace std;

//定義一個基類,名為“圖形”
class shape			
{
//基類私有成員(包括私有成員變數和私有成員函式)
private:			
	//基類私有成員變數
	double x;		//橫座標
	double y;		//縱座標
	double scale;	//尺度
	//基類私有成員函式
	void reset()	//重置橫縱座標和尺度座標
	{
		x = y = 0.0;
		scale = 1.0;
	}
//基類保護成員(包括保護成員變數和保護成員函式)
protected:
	//基類保護成員變數
	int color;				//顏色
	//基類保護成員函式
	void show_color(){}		//顯示顏色(空函式)
//基類公有成員(均為成員函式)
public:				
	shape(double _x, double _y, double _sacle,int _color)		//建構函式
		:x(_x), y(_y), scale(_sacle),color(_color) {}
	virtual ~shape() {}								//解構函式
	void move_x(double _x)		{ x = _x; }			//移動橫座標
	void move_y(double _y)		{ y = _y; }			//移動縱座標
	void resize(double _scale) { scale = _scale; };	//放縮尺度
	void normalize() { reset(); }					//標準化
	void call_show_color(){ show_color(); }			//僅用來呼叫基類保護成員函式
	void access_color() { color = 255; }			//僅用來訪問基類保護成員變數
};

//從“圖形”基類生成一個派生類,名為“長方形”
class rectangule
	:public shape	//繼承“圖形”基類
{
//派生類私有成員
private:			
	//派生類私有成員變數
	double angle;	//長方形朝向角度
	double ratio;	//長方形的長寬比
	//派生類私有成員函式
	void set_ratio(double _ratio) { ratio = _ratio; }	//設定長寬比
//派生類上共有成員(均為成員函式)
public:				
	rectangule									//建構函式
	(double _x,double _y,double _scale,double _angle,int _color)
		:shape(_x,_y,_scale,_color),angle(_angle),ratio(1.0){}
	virtual ~rectangule(){}						//解構函式
	void set_angle(double _angle)				//設定長方形朝向角度
	{
		angle = _angle;
	}
	void set_square()	{ set_ratio(1.0); }		//設定為正方形
	void set_long()		{ set_ratio(2.0); }		//設定為2:1長方形
	//以下操作為示例操作,在此僅為說明繼承中基類的private訪問許可權。
	//void amplify1(){ scale = 10.0;}			//將長方形尺度改為10
	//由於派生類的public成員函式不可訪問scale,失效
	void amplify2() { resize(10.0); }			//將長方形尺度改為10
	//void reset_rect1() { reset(); }			//重置長方形
	//由於派生類的public成員函式不可訪問reset(),失效
	void reset_rect2() { normalize(); }			//重置長方形
	//可見,派生類在基類之上新增加的成員函式,只能直接訪問其新增加的
	//私有成員函式和私有成員變數,而無法訪問其從基類繼承來的私有成員
	//變數和私有成員函式。
	/*此處著重強調一下*/
	//很多教材或者部落格常用的說法都是“派生類只能繼承基類的public部分,
	//而無法繼承其private部分”。其實這樣的說法過於籠統和模糊,容易對
	//初學者造成誤導。
	//更恰當的說法應該是“派生類繼承了基類所有的內容,但它在此基礎上
	//新增加的成員函式無權直接訪問它從基類繼承來的private部分,它只能
	//通過它所繼承的基類的public成員函式來間接訪問它從基類繼承來的
	//private成員變數和成員函式。

	//為派生類增加public成員函式,試圖直接訪問它從基類繼承來的	
    //protected的成員變數和成員函式。結果發現,在派生類中新增加的public
	//成員函式確實有直接訪問基類中protected成員變數和成員函式的許可權。
	void d_call_show_color()	{ show_color(); }	//
	void d_access_color()		{ color = 244; }
	
};

//主函式,測試兩種類物件對其內部各變數的操作許可權
int main()
{
//1. 測試“圖形”物件以說明
//一個普通基類物件對其private成員、protected成員和public成員的訪問許可權。
//類物件s1僅可直接訪問以下屬於public的成員函式。
	
	shape s1(10, 20, 1.0,127);	
	s1.move_x(15);
	s1.move_y(25);
	s1.normalize();
	s1.resize(2.0);
	s1.~shape();
	s1.access_color();
	s1.call_show_color();

	//s1.x = 22;	//成員變數x位於private當中,類物件s1無法直接訪問它。
	//但s1.move_x(15)這個類物件的public成員函式卻可以訪問x。
	//s1.reset();	//成員函式reset()位於private當中,類物件s1無法直接訪問它。
	//但s1.normalize()這個類物件的public成員函式卻可以訪問reset()。
	//s1.color = 255;	//成員變數color位於protected當中,類物件s1無法直接訪問它。
	//但s1.access_color()這個類物件的public成員函式卻可以訪問color。
	//s1.show_color();	//成員函式show_color()位於protected當中,
	//類物件s1無法直接訪問它。
	//但s1.call_show_color()這個類物件的public成員函式卻可以訪問show_color()。

	//綜上,類物件不可以直接訪問private的成員變數和成員函式,
	//只能通過類內部的其他成員函式訪問它們,
	//這就是封裝應有的效果。
	
//2. 測試“長方形”物件以說明
//一個派生類物件對其所繼承的基類的private成員、protected成員和public成員,以及
//其新增添的private成員與public成員的訪問許可權。
//類物件r1僅可以直接訪問以下屬於public的成員函式。

	rectangule r1(11, 21, 1.0, 5.0,127);
	//所繼承的基類自帶的public成員函式
	r1.move_x(1.0);
	r1.move_y(1.2);
	r1.normalize();
	r1.resize(2.0);

	//派生過程中新新增的public成員函式
	r1.amplify2();
	r1.reset_rect2();
	r1.set_angle(5.1);
	r1.set_long();
	r1.set_square();
	r1.~rectangule();
	r1.d_call_show_color();
	r1.d_access_color();

	system("pause");
	return 0;
}

    最後,我總結了一張圖來表示public繼承中的private、protected和public成員之間的訪問關係。

    以上就是我目前學習到的public繼承中所涉及的知識。

    如果圖中有錯誤,請留言告訴我,謝謝!