8.2-C++遠征之繼承篇-下-學習筆記

8.2-C++遠征之繼承篇-下-學習筆記

來自專欄有趣的Python

Is-a 編碼

4-5-SoldierIsAPerson

Person.h:

#include <string>using namespace std;class Person{public:Person(string name = "Person_jim");virtual ~Person();// 虛析構函數,可繼承。soldier內的也會是虛的。void play();protected:string m_strName;};

Person.cpp:

#include "Person.h"#include <iostream>#include <string>using namespace std;Person::Person(string name){m_strName = name;cout << "person()" << endl;}Person::~Person(){cout << "~person()" << endl;}void Person::play(){cout << "person - play()" << endl;cout << m_strName << endl;}

Soldier.h:

#include "Person.h"#include <string>using namespace std;class Soldier:public Person{public:Soldier(string name = "Soldier_james",int age =20);virtual ~Soldier();void work();protected:string m_iAge;};

Soldier.cpp:

#include "Soldier.h"#include <iostream>using namespace std;Soldier::Soldier(string name,int age){m_strName = name;m_iAge = age;cout << "Soldier()" << endl;}Soldier::~Soldier() {cout << "~Soldier()" << endl;}void Soldier::work() {cout << m_strName << endl;cout << m_iAge << endl;cout << "Soldier -- work" << endl;}

main.cpp:

#include <iostream>#include <stdlib.h>#include "Soldier.h"int main(){Soldier soldier;Person p = soldier; p.play();system("pause");return 0;}

可以看到此時調用Person的play(),列印出的m_strName是Solder的數據成員。

int main(){Soldier soldier;Person p; p.play();system("pause");return 0;}

因為Person是有默認構造函數的,因此此時列印出來的必定似乎Person中數據成員。

剛才我們是使用soldier來初始化p,現在我們嘗試使用賦值方式。

int main(){Soldier soldier;Person p;p = soldier;p.play();system("pause");return 0;}

無論是初始化,還是賦值,都可以實現。下面我們試試指針方式。

int main(){Soldier soldier;Person *p;p = &soldier;p->play();system("pause");return 0;}

使用父類的指針,調用子類獨有的函數

p->work();

會提示錯誤:

錯誤 C2039 「work」: 不是「Person」的成員

  • 關於銷毀時,析構函數是如何執行的。

int main(){Person *p = new Soldier;p->play();delete p;p=NULL;system("pause");return 0;}

可以看到:1. 實例化時先執行父類的構造,再執行子類的構造。 父類指針指向子類對象,訪問到的是子類的數據成員。 2. 銷毀時先執行子類析構函數,再執行父類析構函數。

執行結果如上圖所示,是因為我們的Person.h中已經將析構函數添加了virtual關鍵字

virtual ~Person();// 虛析構函數,可繼承。soldier內的也會是虛的。

如果沒有virtual,那麼在銷毀時會造成只執行了父類的析構函數,沒有執行子類的。

新知識點: 虛析構函數

什麼時候會用到:

虛析構函數是為了解決基類的指針指向堆中的派生類對象,希望使用基類的指針釋放內存。

這個關鍵字是會被繼承下去的,即使在Soldier中不寫,也會是一個虛析構函數

is-A 編碼二

4-6-SoldierIsAPerson2:

main.cpp:

#include <iostream>#include <stdlib.h>#include "Soldier.h"void test1(Person p){p.play();}void test2(Person &p){p.play();}void test3(Person *p){p->play();}int main(){Person p;Soldier s;test1(p);test1(s);system("pause");return 0;}

運行結果前三行是實例化了Person和Soldier的輸出。

test1中調用了傳入的Person的play方法,列印處理Person_jim。

test1傳入s,調用了Person的play方法,數據成員是Soldier的。

銷毀了兩次Person,是因為調用時,有一個臨時的對象person

兩次析構函數是在兩個test1執行完之後自動執行的。因為此時傳入一個臨時變數p。用完就要銷毀掉。

test2(p);test2(s);

運行結果:

因為傳入的是引用。所以裡面調用的仍是傳入的對象本身。沒有實例化臨時變數。

test3(&p);test3(&s);

與test2實驗結果完全一致。p分別調用基類和派生類的play

結論:test2 和 test3 不會產生新的臨時變數,效率更高。

鞏固練習

定義兩個類,基類是人類,定義數據成員姓名(name),及成員函數void attack()。

士兵類從人類派生,定義與人類同名的數據成員姓名(name)和成員函數void attack()。

通過對同名數據成員及成員函數的訪問理解成員隱藏的概念及訪問數據的方法。

#include <iostream>#include <stdlib.h>#include <string>using namespace std;/*** 定義人類: Person* 數據成員: m_strName* 成員函數: attack()*/class Person{public:string m_strName;void attack(){cout << "attack" << endl;}};/*** 定義士兵類: Soldier* 士兵類公有繼承人類* 數據成員: m_strName* 成員函數: attack()*/class Soldier:public Person{public:string m_strName;void attack(){cout << "fire!!!" << endl;}};int main(void){// 實例士兵對象Soldier soldier;// 向士兵屬性賦值"tomato"soldier.m_strName = "tomato";// 通過士兵對象向人類屬性賦值"Jim"soldier.Person::m_strName = "Jim";// 列印士兵對象的屬性值cout << soldier.m_strName << endl;// 通過士兵對象列印人類屬性值cout << soldier.Person::m_strName << endl;// 調用士兵對象方法soldier.attack();// 通過士兵對象調用人類方法soldier.Person::attack();return 0;}

多繼承與多重繼承

多重繼承:

上述關係具體到代碼上可以這樣寫:

class Person{};class Soldier: public Person{};class Infantryman: public Soldier{};

多繼承:

具體到代碼層面,如下:

class Worker{};class Farmer{};class MigrantWorker: public Worker,public Farmer{};

多繼承時中間要加逗號,並且要寫清楚繼承方式。如果不寫,那麼系統默認為private繼承

多重繼承代碼演示

Person -> Soldier -> Infantryman

  • 孫子實例化時先實例化爺爺,然後實例化爸爸。最後才能實例化孫子。

  • 孫子先死。然後爸爸死。最後爺爺死。

附錄代碼 5-2-Multi-Inherit:

Person.h

#include <string>using namespace std;class Person{public:Person(string name = "jim");virtual ~Person();// 虛析構函數,可繼承。soldier內的也會是虛的。void play();protected:string m_strName;};

Person.cpp

#include "Person.h"#include <iostream>#include <string>using namespace std;Person::Person(string name){m_strName = name;cout << "person()" << endl;}Person::~Person(){cout << "~person()" << endl;}void Person::play(){cout << "person - play()" << endl;cout << m_strName << endl;}

Soldier.h:

#include "Person.h"#include <string>using namespace std;class Soldier :public Person{public:Soldier(string name = "james", int age = 20);virtual ~Soldier();void work();protected:string m_iAge;};

Soldier.cpp:

#include "Soldier.h"#include <iostream>using namespace std;Soldier::Soldier(string name, int age){m_strName = name;m_iAge = age;cout << "Soldier()" << endl;}Soldier::~Soldier() {cout << "~Soldier()" << endl;}void Soldier::work() {cout << m_strName << endl;cout << m_iAge << endl;cout << "Soldier -- work" << endl;}

Infantry.h:

#include "Soldier.h"class Infantry:public Soldier {public:Infantry(string name = "jack", int age = 30);~Infantry();void attack();};

Infantry.cpp

#include <iostream>#include "Infantry.h"using namespace std;Infantry::Infantry(string name /* = "jack" */, int age /* = 30 */){m_strName = name;m_iAge = age;cout << "Infantry()" << endl;}Infantry::~Infantry(){cout << "~Infantry()" << endl;}void Infantry::attack() {cout << m_iAge << endl;cout << m_strName<< endl;cout << "Infantry --attack" << endl;}

main.cpp:

#include <iostream>#include <stdlib.h>#include "Infantry.h"void test1(Person p){p.play();}void test2(Person &p){p.play();}void test3(Person *p){p->play();}int main(){Infantry infantry;system("pause");return 0;}

可以看到,如我們預期的,先生爺爺,再生爸爸,最後生兒子。

步兵 IsA 軍人 IsA 人類

具有傳遞關係。

int main(){Infantry infantry;test1(infantry);test2(infantry);test3(&infantry);system("pause");return 0;}

test1傳入的是對象。所以會有臨時生成的對象。然後銷毀。

c++多繼承

實例化兩個父類的順序與繼承時冒號後順序一致而與初始化列表順序無關。

class MigrantWorker:public Worker, public Farmer

  • 是按繼承的聲明順序來構造超類的 不是按初始化列表的順序
  • 函數參數默認值最好在聲明時設置而不是在定義時。是因為定義出現在調用後 導致編譯其無法識別 然後報錯

完整代碼 5-3-Multi-TwoInherit:

Worker.h:

#include <string>using namespace std;class Worker{public:Worker(string code ="001");virtual ~Worker();void carry();protected:string m_strCode;};

Worker.cpp

#include "Worker.h"#include <iostream>using namespace std;Worker::Worker(string code){m_strCode = code;cout << "worker()" << endl;}Worker::~Worker(){cout << "~worker" << endl;}void Worker::carry(){cout << m_strCode << endl;cout << "worker -- carry()" << endl;}

Farmer.h

#include <string>using namespace std;class Farmer{public:Farmer(string name = "jack");virtual ~Farmer();void sow();protected:string m_strName;};

Farmer.cpp

#include "Farmer.h"#include <iostream>using namespace std;Farmer::Farmer(string name){m_strName = name;cout << "Farmer()" << endl;}Farmer::~Farmer(){cout << "~Farmer()" << endl;}void Farmer::sow(){cout << m_strName << endl;cout << "sow()" << endl;}

MigrantWorker.h

#include <string>#include "Farmer.h"#include "Worker.h"using namespace std;class MigrantWorker:public Worker, public Farmer//此處順序決定實例化順序。{public:MigrantWorker(string name,string code);~ MigrantWorker();private:};

MigrantWorker.cpp

#include "MigrantWorker.h"#include <iostream>using namespace std;MigrantWorker::MigrantWorker(string name,string code): Farmer(name), Worker(code){cout <<":MigrantWorker()" << endl;}MigrantWorker::~MigrantWorker(){cout << "~MigrantWorker()" << endl;}

重點代碼:

MigrantWorker::MigrantWorker(string name,string code): Farmer(name), Worker(code)

將其中的name傳給Farmer code傳給Worker

main.cpp

#include <iostream>#include "MigrantWorker.h"#include <stdlib.h>int main(){// 堆方式實例化農民工對象MigrantWorker *p = new MigrantWorker("jim","100");p->carry(); // 工人成員函數p->sow(); // 農民成員函數delete p;p = NULL;system("pause");return 0;}

  • 可以用指向子類的指針p,調用他兩個爸爸的方法。
  • 實例化順序與聲明順序一致,聲明時先寫的Worker。
  • 銷毀順序與實例化正好相反

基類不需要定義虛析構函數,虛析構函數是在父類指針指向子類對象的時候使用的。 這裡只是簡單的實例化子類對象而已,銷毀的時候會執行父類和子類的析構函數的

  • 虛析構函數是為了解決基類的指針指向堆中的派生類對象,並用基類的指針刪除派生類對象。

鞏固練習

  • 定義worker工人類及children兒童類
  • worker類中定義數據成員m_strName姓名
  • children類中定義成員m_iAge年齡
  • 定義ChildLabourer童工類,公有繼承工人類和兒童類
  • 在main函數中通過new實例化ChildLabourer類的對象,並通過該對象調用worker及children類中的成員函數,最後銷毀該對象,體會多繼承的繼承特性及構造函數及析構函數的執行順序。

#include <iostream>#include <stdlib.h>#include <string>using namespace std;/*** 定義工人類: Worker* 數據成員: m_strName* 成員函數: work()*/class Worker{public:Worker(string name){m_strName = name;cout << "Worker" << endl;}~Worker(){cout << "~Worker" << endl;}void work(){cout << m_strName << endl;cout << "work" << endl;}protected:string m_strName;};/*** 定義兒童類: Children* 數據成員: m_iAge* 成員函數: play()*/class Children{public:Children(int age){m_iAge = age;cout << "Children" << endl;} ~Children(){cout << "~Children" << endl;} void play(){cout << m_iAge << endl;cout << "play" << endl;}protected:int m_iAge;};/*** 定義童工類: ChildLabourer* 公有繼承工人類和兒童類*/class ChildLabourer : public Children, public Worker{public:ChildLabourer(string name, int age):Worker(name),Children(age){cout << "ChildLabourer" << endl;}~ChildLabourer(){cout << "~ChildLabourer" << endl;} };int main(void){// 使用new關鍵字創建童工類對象ChildLabourer *p = new ChildLabourer("jim",12);// 通過童工對象調用父類的work()和play()方法p-> work();p->play();// 釋放delete p;p = NULL;return 0;}

c++虛繼承(理論)

多繼承 & 多重繼承的煩惱

  • 多繼承和多重繼承會出現問題呢?

菱形繼承中既有多繼承也有多重繼承。D中將含有兩個完全一樣的A的數據。

如圖,假設類a是父類,b類和c類都繼承了a類,而d類又繼承了b和c,那麼由於d類進行了兩次多重繼承a類,就會出現兩份相同的a的數據成員或成員函數,就會出現代碼冗餘。

人 -> 農民 | 工人-> 農民工

如何避免該情況的發生,就可以使用虛繼承

虛繼承是一種繼承方式,關鍵字是virtual

class Worker: virtual public Person // 等價於 public virtual Person{};class Farmer: virtual public Person // 等價於 public virtual Person{};class MigrantWorker: public Worker,public Farmer{};

使用虛繼承。那麼農民工類將只有一份繼承到的Person成員。

虛繼承編碼(一)

我們在Farmer中和worker中都引入了Person.h,當MigrantWorker繼承自他們兩。會引入兩個Person的數據成員等。

  • 通過宏定義解決重定義:

我們在被公共繼承的類中應該這樣寫:

#ifndef PERSON_H//假如沒有定義#define PERSON_H//定義//代碼#endif //結束符

附錄完整代碼: 6-2-VirtualInherit

Person.h:

#ifndef PERSON_H//假如沒有定義#define PERSON_H//定義#include <string>using namespace std;class Person{public:Person(string color = "blue");virtual ~Person();void printColor();protected:string m_strColor;};#endif //結束符

Person.cpp:

#include <iostream>#include "Person.h"using namespace std;Person::Person(string color){m_strColor = color;cout << "person()" << endl;}Person::~Person(){cout << "~Person()" << endl;}void Person::printColor(){cout << m_strColor << endl;cout << "Person -- printColor" << endl;}

Worker.h

#include <string>using namespace std;#include "Person.h"class Worker:public Person{public:Worker(string code ="001",string color ="red");// 希望worker可以傳入膚色給personvirtual ~Worker();void carry();protected:string m_strCode;};

Worker.cpp

#include "Worker.h"#include <iostream>using namespace std;Worker::Worker(string code,string color):Person(color){m_strCode = code;cout << "worker()" << endl;}Worker::~Worker(){cout << "~worker" << endl;}void Worker::carry(){cout << m_strCode << endl;cout << "worker -- carry()" << endl;}

Farmer.h

#include <string>using namespace std;#include "Person.h"class Farmer:public Person{public:Farmer(string name = "jack",string color = "blue");virtual ~Farmer();void sow();protected:string m_strName;};

Farmer.cpp

#include "Farmer.h"#include <iostream>using namespace std;Farmer::Farmer(string name,string color):Person(color){m_strName = name;cout << "Farmer()" << endl;}Farmer::~Farmer(){cout << "~Farmer()" << endl;}void Farmer::sow(){cout << m_strName << endl;cout << "sow()" << endl;}

MigrantWorker.h

#include <string>#include "Farmer.h"#include "Worker.h"using namespace std;class MigrantWorker:public Worker, public Farmer//此處順序決定實例化順序。{public:MigrantWorker(string name,string code,string color);~ MigrantWorker();private:};

MigrantWorker.cpp

#include "MigrantWorker.h"#include <iostream>using namespace std;MigrantWorker::MigrantWorker(string name,string code,string color): Farmer(name,color), Worker(code,color){cout <<":MigrantWorker()" << endl;}MigrantWorker::~MigrantWorker(){cout << "~MigrantWorker()" << endl;}

main.cpp

#include <iostream>#include "MigrantWorker.h"#include <stdlib.h>int main(){system("pause");return 0;}

如果不加宏定義會報錯,Person類被重定義了。

錯誤 C2011 「Person」:「class」類型重定義

因為我們在Worker和Farmer中都引入了Person.h,這是正常的,但是當MigrantWorker類繼承上面兩個類,就會引入兩遍Person。

公共繼承的類需要寫上,不是公共繼承的也最好寫上,因為未來可能會被重定義。

推薦寫文件的全稱大寫,但是其實這個是自定義的,只要能區分開其他文件就可以。

可以正常通過編譯說明我們用宏定義成功的解決了菱形問題的重定義報錯。

c++虛繼承(編碼二)

與上小節中其他代碼相同。

main.cpp:

int main(){MigrantWorker *p = new MigrantWorker("merry", "200", "yellow");delete p;p = NULL;system("pause");return 0;}

可以看到Person的構造函數執行了兩遍。

現在MigrantWorker存在兩份Person的成員,下面我們來證明。

  1. 修改Worker.cpp和Farmer.cpp:

Worker::Worker(string code,string color):Person("Worker"+color)Farmer::Farmer(string name,string color):Person("Farmer"+color)

  1. 通過農民工的指針列印這兩份值。

main.cpp

int main(){MigrantWorker *p = new MigrantWorker("merry", "200", "yellow");p->Farmer::printColor();p->Worker::printColor();p = NULL;system("pause");return 0;}

可以看到在農民工對象中確實是有兩份數據成員color的。

使用虛繼承,讓農民工只有一份。

Worker.h & Farmer.h中修改

class Worker: virtual public Person//work是虛基類。{};class Farmer: virtual public Person{};

可以看到Person的構造函數只被執行了一次。blue的原因是,既然兩個兒子不知道哪個對孫子好,爺爺隔代親傳。

鞏固練習

  • 定義Person人類,worker工人類及children兒童類,
  • worker類中定義數據成員m_strName姓名,
  • children類中定義成員m_iAge年齡,
  • worker類及children類均虛公有繼承Person類,
  • 定義ChildLabourer童工類,公有繼承工人類和兒童類,從而形成菱形繼承關係
  • 在main函數中通過new實例化ChildLabourer類的對象,並通過該對象調用Person,Worker及Children類中的成員函數,最後銷毀該對象,掌握多重繼承,多繼承,虛繼承的定義方法。

#include <iostream>#include <stdlib.h>#include <string>using namespace std;/*** 定義人類: Person*/class Person{public:Person(){cout << "Person" << endl;}~Person(){cout << "~Person" << endl;}void eat(){cout << "eat" << endl;}};/*** 定義工人類: Worker* 虛繼承人類*/class Worker : virtual public Person{public:Worker(string name){m_strName = name;cout << "Worker" << endl;}~Worker(){cout << "~Worker" << endl;}void work(){cout << m_strName << endl;cout << "work" << endl;}protected:string m_strName;};/*** 定義兒童類:Children* 虛繼承人類*/class Children : virtual public Person{public:Children(int age){m_iAge = age;cout << "Children" << endl;} ~Children(){cout << "~Children" << endl;} void play(){cout << m_iAge << endl;cout << "play" << endl;}protected:int m_iAge;};/*** 定義童工類:ChildLabourer* 公有繼承工人類和兒童類*/class ChildLabourer: public Children,public Worker{public:ChildLabourer(string name, int age):Worker(name),Children(age){cout << "ChildLabourer" << endl;}~ChildLabourer(){cout << "~ChildLabourer" << endl;} };int main(void){// 用new關鍵字實例化童工類對象ChildLabourer *p = new ChildLabourer("11",12);// 調用童工類對象各方法。p->eat();p->work();p->play();delete p;p = NULL;return 0;}

運行結果:

可以看到多繼承中實例化順序,先聲明的哪個先實例化哪個,與初始化列表順序無關。

推薦閱讀:

心血來潮,試試專欄。
Android 64 bit的一些兼容性分析
HTTP協議幾個版本的比較
代碼整潔之道(二)優雅注釋之道
登錄失敗:未授予用戶在此計算機上的請求登錄類型

TAG:計算機科學 | C | C編程 |