適合具備 C 語言基礎的 C++ 教程之五
前言
在上述教程中,我們已經完成了 C++相對于 C語言來說獨特的語法部分,在接下來的教程中,我們將敘述 C++中面向對象的語法特性。我們在學習面向對象的這種編程方法的時候,常常會聽到這三個詞,封裝、繼承、派生,這也是面向對象編程的三大特性,在本節我們將依次闡述封裝、繼承、派生的具體用法,在這里,我們先敘述的是封裝這個屬性的的相關內容。下圖是關于 封裝 這個特性所包含的一些內容。
封裝
下圖就是封裝所具備的相關特性:
image-20210209204824118
那么上圖所示的抽象出數據成員以及成員函數具體的含義是什么呢,正如前面教程所述,在前面的教程里,我們選用一個 Person類來作為例子進行講解,其中這個類里我們有 name以及age,這個也就是我們抽象出來的數據,那抽象出來的成員函數也就是前面教程講到的setName()和setAge()函數,在設計這個類的時候,會把這個類的一些成員設置為私有的或者公有的,這也就是訪問控制。具體的代碼如下所示:
- /* 為了代碼簡便,省略相關構造函數以及析構函數,為的是展示封裝的特性*/
- class Person {
- private:
- char *name;
- int age;
- public:
- Person()
- {
- cout << "Person" << endl;
- name = NULL;
- }
- ~Person()
- {
- cout << "~Person()" << endl;
- if (this->name)
- {
- delete this->name;
- }
- }
- void setName(char *name)
- {
- if (this->name) {
- delete this->name;
- }
- this->name = new char[strlen(name) + 1];
- strcpy(this->name, name);
- }
- int setAge(int a)
- {
- if (a < 0 || a > 150)
- {
- age = 0;
- return -1;
- }
- age = a;
- return 0;
- }
- };
繼承
繼承的含義就如其字面意思一樣,用更加專業的話來說,就是從基類繼承相關屬性,而這個新的類就叫做派生類。下面這個示意圖也表明了繼承所帶來的代碼的簡潔與方便。
image-20210209211013964
就如上述這張圖所示,一個人肯定具有名字和年齡這兩個屬性,那作為一個學生來講,他也必定具備名字和年齡這兩個屬性,那這個時候是要在 Student類里重新定義這些屬性么?顯然,因為引入了繼承這個特性,只需要繼承Person類,那么Student就具備 Person類的相關屬性。在上述代碼的基礎上,我們增加如下所示的代碼:
- /* 注意是在上述代碼的基礎上 */
- class Student : public Person
- {
- };
- int main(int argc, char **argv)
- {
- Student s;
- s.setName("zhangsan");
- s.setAge(16);
- s.printInfo();
- return 0;
- }
上述代碼中,Student類是繼承自 Person類的,我們可以看到在上述所示的Student類中,并沒有setName和 setAge的成員函數,但是在定義的 Student實例中,卻能夠適用 setName和 setAge的成員函數,這也就說明了 Student類已經繼承了 Person類。
繼承后的訪問控制
private
一個派生類從一個基類繼承而來,而繼承的方式有多種,可以是私有繼承,也可以是公有繼承,同時也可以是保護繼承。那么這個時候基類的各個數據成員的訪問屬性又是怎么樣的呢,我們來看一下下面這張圖,其展現了以各種方式繼承自基類的派生類的數據成員的屬性。
image-20210209223145289
從這個表可以清楚地知道基類的訪問屬性與派生類的訪問屬性的對應情況。同樣的,我們用一個簡單的例子來說明這個知識點:
- class Father
- {
- private:
- int money;
- public:
- void it_skill(void)
- {
- cout << "The father's it skill" <<endl;
- }
- int getMoney(void)
- {
- return money;
- }
- void setMoney(int money)
- {
- this->money = money;
- }
- };
這個是基類的數據成員以及成員函數,為了更好的說明繼承后的數據的屬性,我們定義一個 son類,代碼如下所示:
- class Son : public Father
- {
- private:
- int toy;
- public:
- void play_game(void)
- {
- cout << "play_game()" << endl;
- int m;
- //money -= 1; /* 錯誤的代碼 */
- m = getMoney();
- m--;
- setMoney(m);
- }
- };
上述定義了兩個類,一個是 Father類,一個是 Son類,Son類繼承于 Father類,這兩個類用通俗的語言進行解釋便是,父親有自己的私房錢,兒子有自己的玩具,父親有一項技能是 it,兒子呢比較喜歡玩游戲。因為是繼承,所以兒子類具有父親類的相關屬性,但是,作為兒子是不能夠直接去父親兜里拿錢的,那會被揍,但是如果兒子需要錢,可以向父親要。這對應的代碼也就是上述中 money -= 1,但是這是錯誤的,不能直接從父親的兜里拿錢,而剩余的三句代碼的意思也就相當于是向父親要錢。用專業的話來講也就是:派生類不能夠訪問基類的私有成員,緊接著是主函數的代碼:
- int main(int argc, char **argv)
- {
- Son s;
- s.it_skill();
- s.setMoney(10);
- cout << "The money is:" << s.getMoney() << endl;
- s.play_game();
- return 0;
- }
代碼輸出的結果如下所示:
image-20210209232507917
protected
還是采用比較通俗的話來敘述這一知識點,兒子相對于父親的關系自然是與其他人有所不同的,比如有一把父親房間門的鑰匙,對于兒子來說是可以拿到的,但是對于外人來說,這是不可訪問的。那在程序中要如何實現這么一個功能呢?這里就要引入 protected了。代碼如下所示:
- class Father {
- private:
- int money;
- protected:
- int room_key; /* 增添的 room_key */
- public:
- void it_skill(void)
- {
- cout<<"father's it skill"<<endl;
- }
- int getMoney(void)
- {
- return money;
- }
- void setMoney(int money)
- {
- this->money = money;
- }
- };
我們可以看到在 Father類中,增添了一項就是 protected修飾的 room_key,緊接著我們來看Son類的代碼:
- class Son : public Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son paly game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- /* 外人不能拿父親的房間鑰匙
- * 兒子可以
- */
- room_key = 1;
- }
- };
我們看到,這個時候,是可以在 Son類里面直接操作使用 protected修飾的 room_key的。在這里總結一下就是:派生類可以直接訪問到基類用 protected 修飾的數據成員。接下來,我們繼續看主函數的代碼:
- int main(int argc, char **argv)
- {
- Son s;
- s.setMoney(10);
- cout << s.getMoney()<<endl;
- s.it_skill();
- s.play_game();
- //s.room_key = 1;
- return 0;
- }
通過上述代碼可以看到 s.room_key = 1這條語句被注釋了,這條語句是錯誤的,雖然基類使用了 protected修飾了 room_key,但是在主函數中,仍然是不能夠直接訪問 room_key的。
調整訪問控制
依舊采用比較通俗的話來闡述,如果兒子從父親那里繼承了一些東西,那這個時候,繼承得到的這些東西的處理權就全在兒子了。在程序里面也是同樣的道理,我們在上述代碼的基礎上進行更改,Father類不變,改變 Son類。代碼如下所示:
- class Son : public Father {
- private:
- int toy;
- public:
- using Father::room_key;
- void play_game(void)
- {
- int m;
- cout<<"son paly game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
上述代碼中,我們可以看到在 public的作用域內,我們使用 using Father::room_key將 room_key的屬性更改為 public,做了這樣的更改之后,我們就可以在主函數里直接訪問 room_key了。代碼如下所示:
- int main(int argc, char **argv)
- {
- Son s;
- s.setMoney(10);
- cout << s.getMoney()<<endl;
- s.it_skill();
- s.play_game();
- s.room_key = 1;
- return 0;
- }
上述代碼是可以運行的,也說明這種方式是可行的。但是如果想要將 money的屬性更改為 public,也就是增加如下所示的代碼:
- class Son : public Father {
- private:
- int toy;
- public:
- using Father::room_key;
- using Father::money;
- void play_game(void)
- {
- int m;
- cout<<"son paly game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
那么編譯將不會通過,錯誤信息如下所示:
image-20210210001456319
說明這種方法是不可行的,這是為什么呢?是因為對于 Son來說,money本身就是它不能訪問到的數據,那么自然也就不能夠對其屬性進行更改了。換句更加專業的話來敘述也就是:在調整訪問控制的時候,只有類本身能夠訪問到的數據才能調整它的訪問控制,如果其本身對于這個類就是不能夠訪問的,那么也就無法對其進行更改。
那上述可以說是提升訪問控制,同樣的,也可以降低訪問控制,比如說上述的 it_skill,如果不想把這個屬性繼續繼承下去或者說不讓外部能夠訪問到它,那么也可以降低它的訪問控制,降低的方法跟提升的方法是一樣的,只需要在 private中加上一句代碼就可以,加了的代碼如下所示:
- class Son : public Father
- {
- private:
- int toy;
- using Father::it_skill;
- public:
- /* 省略 */
- };
因此,只要對于派生類能夠看到的數據成員或者成員函數,它都能夠提高或者降低它的訪問控制。
三種不同繼承方式的差異
在上述的內容中,我們提到了派生類在繼承基類的時候,存在不同的繼承方式,不同的繼承方式對數據成員的使用以及其成員函數的調用存在不同的影響,下面分別是三種不同的繼承方式:public和 private以及protected,代碼如下所示:
- /* 以 public 方式繼承 */
- class Son_pub : public Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son play game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
- /* 以 private 方式繼承 */
- class Son_pri : private Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son play game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
- /* 以 protected 方式繼承 */
- class Son_pro : protected Father {
- private:
- int toy;
- public:
- void play_game(void)
- {
- int m;
- cout<<"son play game"<<endl;
- m = getMoney();
- m--;
- setMoney(m);
- room_key = 1;
- }
- };
上述代碼就是以三種不同方式從 Father類得到的 Son類,每一種繼承方式存在什么不同呢,我們通過主函數來說明這個問題:
- int main(int argc, char **argv)
- {
- Son_pub s_pub;
- Son_pro s_pro;
- Son_pri s_pri;
- s_pub.play_game();
- s_pro.play_game();
- s_pri.play_game();
- s_pub.it_skill();
- //s_pro.it_skill(); // error
- //s_pri.it_skill(); // error
- return 0;
- }
通過上述代碼,并對照上述那種表,我們可以知道,無論是何種繼承方式,派生類內部public的成員函數都是可以使用的,而對于從基類繼承得到的成員函數,如果是以 protected和private方式來繼承的話,那么是不能夠在主函數進行調用的,因此上述代碼中注釋掉的兩句后面表明了錯誤。
上述的代碼所展示的是一層的繼承,我們在繼承得到的派生類 Son的基礎上繼續繼承得到 Grandson,首先我們先在 Father類里新增加一個public的數據成員,增加的代碼如下所示:
- class Father
- {
- private:
- int money;
- protected:
- int room_key;
- public:
- int address;
- /*其余不改動,省略*/
- };
增加了上述Father類的代碼之后,我們來看 Grandson_pub類的代碼:
- class Grandson_pub : public Son_pub
- {
- public:
- void test(void)
- {
- room_key = 1; /* room_key is protected */
- address = 2; /* address is public */
- }
- };
上述代碼中,Grandson_pub是以 public的方式從 Son_pub繼承而來,room_key在 Father類是 protected,在 Son_pub類也是 protected,那么在這里也是 protected,而對于 address來說,它在 Father類里是 public,在 Son_pub里也是 public,在這里也是 public,所以在這里都能夠訪問到。
緊接著來看,Grandson_pro類的代碼:
- class Grandson_pro : public Son_pro
- {
- public:
- void test(void)
- {
- room_key = 1; /* room_key is protected */
- address = 2; /* address is protected */
- }
- };
上述中,Grandson_pro是以 public的方式從 Son_pro中繼承得到的,以剛剛那種分析的思路我們能夠分析得出 room_key當前是 protected以及 address是 protected,那么當前的數據成員在這也就是都能夠訪問的了。
繼續來看Grandson_pri類的代碼,代碼如下所示:
- class Grandson_pri : public Son_pri
- {
- public:
- void test(void)
- {
- //room_key = 1; /* room_key is private */
- //address = 2; /* address is private */
- }
- };
上述中,Grandson_pri是以 public的方式從 Son_pri中繼承得來,同樣按照上述的分析方法,我們能夠分析出 room_key和 address都是 private的,既然是 private的,那么也就不能夠進行訪問,因此上述代碼中,我們將兩句代碼進行了注釋。
小結
上述就是本次分享的關于封裝以及繼承的相關內容,主要是關于繼承之后數據成員的訪問控制,以及通過不同的方式進行繼承時的數據成員的訪問控制。
上述教程所涉及的代碼可以通過百度云鏈接的方式獲取到,下面是百度云鏈接:
鏈接:https://pan.baidu.com/s/18AGYqxkxsEcR4ZW6_Nhevg
提取碼:dlst
本文轉載自微信公眾號「 wenzi嵌入式軟件」,可以通過以下二維碼關注。轉載本文請聯系 wenzi嵌入式軟件公眾號。