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繼承中所涉及的知識。
如果圖中有錯誤,請留言告訴我,謝謝!