整個C++遠征計劃: 起航->離港->封裝->繼承ios
現實生活中: 兒子繼承父親財產c++
爲何要有繼承?ide
從下面的例子提及函數
人類:編碼
class Person { public: void eat(); string m_strName; // 名字 int m_iAge; // 年齡 }
工人(人類的一種):spa
class Worker { public: void eat(); void work(); // 能夠工做 string m_strName; int m_iAge; int m_iSalary; // 會發工資 }
代碼重用,前提條件是發生關係的這兩個類是超集子集的關係。3d
class Worker: public Person { public: void work(); int m_iSalary; }
此時工人類是人類的派生類。人類是工人類的基類。只須要寫本身特有的。指針
這裏有一個配對的概念,提到基類的時候,對應的是派生類。提到父類時,與之對應的是子類。code
內存中的對象:cdn
人類若是實例化一個對象,就有兩個數據成員。雖然在工人類定義姓名年齡,但由於繼承,工人類也包含姓名和年齡。
要求:
2-2-InheritWorkerPerson
Person.h:
#include <string> using namespace std; class Person { public: Person(); ~ Person(); void eat(); string m_strName; int m_iAge; };
Person.cpp:
#include "Person.h" #include <iostream> using namespace std; Person::Person() { cout << "Person()" << endl; } Person::~Person() { cout << "~Person()" << endl; } void Person::eat() { cout << "eat()" << endl; }
worker.h:
#include "Person.h" // 要求採用公有繼承 class Worker:public Person { public: Worker(); ~Worker(); void work(); int m_iSalary; };
worker.cpp:
#include "Worker.h" #include <iostream> using namespace std; Worker::Worker() { cout << "Worker()" << endl; } Worker::~Worker() { cout << "~Worker()" << endl; } void Worker::work() { cout << "Work" << endl; }
main.cpp:
#include "Worker.h" #include <stdlib.h> #include <iostream> using namespace std; int main(void) { // 堆中申請內存 Worker *p = new Worker(); p->m_strName = "mtianyan"; p->m_iAge = 21; p->eat(); p->m_iSalary = 10000; p->work(); delete p; p = NULL; system("pause"); return 0; }
能夠看到實例化worker的時候,先實例化出爸爸Person,而後生出兒子Worker。
銷燬時,先銷燬兒子Worker,再銷燬掉了Person。
main.cpp
#include "Worker.h" #include <stdlib.h> #include <iostream> using namespace std; int main(void) { // 棧中申請內存 Worker worker; worker.m_strName = "Jim"; worker.m_iAge = 10; worker.eat(); worker.m_iSalary = 1000; worker.work(); system("pause"); return 0; }
棧中申請內存,也是先有爸爸,而後生兒子。 銷燬時,兒子先走,爸爸後走。
class A:public B
class A:protected B
class A:private B
class Person { public: void eat(); string m_strName; int m_iAge; };
class Worker:public Person { public: // void eat(); void work(); // int m_iAge; // string m_strName; int m_iSalary; };
註釋的是不用寫,可是已經包含在Worker中。
int main(void) { Worker worker; worker.m_strName = "Merry"; worker.eat(); return 0; }
使用時,Worker能夠調用父類的數據成員和成員函數。
Protected 和 private 不涉及繼承時,他們兩是同樣的。
private和protected在繼承時的區別:
將剛纔的例子進行修改:
class Person { public: void eat(); protected: int m_iAge; private: string m_strName; };
int main() { Person person; person.eat(); //Public, 必須正確 person.m_iAge = 20; //Protected,禁止訪問 person.m_strName = "jim"; //private,禁止訪問 return 0; }
可是實現成員函數時。咱們的私有,保護數據成員也是能夠正常訪問的。
void Person::eat() { m_strName = "jim"; m_iAge = 10; }
但當protected遇到繼承
class Person { public: void eat(); protected: string m_strName; int m_iAge; }; class Worker:public Person { public: // void eat(); void work(){m_iAge = 20;}; //繼承下來以後能夠經過work訪問到age protected: // int m_iAge; // string m_strName; int m_iSalary; };
公有繼承時,public 的繼承到public。 protected的繼承到protected。
class Person { public: void eat(); private: string m_strName; int m_iAge; }; class Worker:public Person { public: // void eat(); void work(){m_iAge = 20;}; private: int m_iSalary; };
public的繼承到public。父類中的private被繼承到了不可見位置。
而不是private部分。因此,此時經過work操做父類的m_iage會出錯。
要求:
3-2-PublicInheritWorkerPerson
初始化時代碼與上次示例代碼保持一致,下面開始進行修改。
main.cpp:
#include "Worker.h" #include <stdlib.h> #include <iostream> using namespace std; int main(void) { // 棧中申請內存 Worker worker; worker.m_strName = "Jim"; worker.m_iAge = 10; worker.eat(); worker.m_iSalary = 1000; worker.work(); system("pause"); return 0; }
能夠看到運行結果沒有發生變化,依然能夠從子類調用父類的成員函數,數據成員。
protected: string m_strName; private: int m_iAge;
Person.h:
#include <string> using namespace std; class Person { public: Person(); ~ Person(); void eat(); protected: string m_strName; private: int m_iAge; };
Person.cpp中修改eat方法,讓其訪問protected和private下的數據成員。
#include "Person.h" #include <iostream> using namespace std; Person::Person() { cout << "Person()" << endl; } Person::~Person() { cout << "~Person()" << endl; } void Person::eat() { m_strName = "mtianyan"; m_iAge = 21; cout << "eat()" << endl; }
main.cpp將Person.h引入。
#include <stdlib.h> #include <iostream> #include "Person.h" using namespace std; int main(void) { Person person; person.eat(); // 能夠正常訪問 // person.m_strName = "mtianyan666"; //沒法訪問 protected 成員 // person.m_iAge = 21; //沒法訪問 private 成員 system("pause"); return 0; }
類本身的public成員函數,函數內部能夠訪問到類自身的保護以及私有成員變量。
而類內受保護的私有成員,保護成員;沒法在類外直接被訪問。
公有繼承以後父類的私有數據成員被子類繼承到不可見位置,沒法正常使用。
父類中的protected成員,子類也能夠正常訪問。被放在了子類的protected部分。
將m_strName,m_iAge放在protected下。
class Person { public: Person(); ~ Person(); void eat(); protected: string m_strName; int m_iAge; };
在子類中訪問父類的protected數據成員。
void Worker::work() { m_iAge = 21; m_strName = "mtianyan"; cout << "Work" << endl; }
int main(void) { Worker worker; worker.work(); system("pause"); return 0; }
結果:正常運行。
將上面代碼中person下的protected改成private
則會報錯:
Person::m_iAge」: 沒法訪問 private 成員(在「Person」類中聲明)
class A:public B
class A:protected B
class A:private B
Private繼承過來,會將父類的public和protected都降級爲子類的private
例子:
class Line{ public: Line(int x1,int y1, int x2,int y2); private: Coordinate m_coorA; //線段只能訪問到m_coorA的公有數據成員和公有成員函數 Coordinate m_coorB; }
在私有繼承中,子類也只能訪問父類的公有數據成員和公有成員函數。
由於子類對象包含父類: 只能訪問父類當中公有數據成員和成員函數。
要求:
protected繼承:
public成員能夠一直protected繼承下去,被繼承後屬於protected,不能夠被對象直接訪問;能夠被類自身的成員函數訪問。
protected成員能夠一直protected繼承下去,被繼承後屬於protected,不能夠被對象直接訪問;
private成員不能被protected繼承。
3-4-ProtectedPrivateInherit
人類派生出軍人,軍人派生出步兵。
Person.h:
#include <string> using namespace std; class Person { public: Person(); void play(); protected: string m_strName; };
Person.cpp:
#include <iostream> #include "Person.h" using namespace std; Person::Person() { m_strName = "merry"; } void Person::play() { cout << "person - play" << endl; cout << m_strName << endl; }
Soldier.h:
#include "Person.h" class Soldier:public Person { public: Soldier(); void work(); private: int m_iAge; };
Soldier.cpp:
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier() { } void Soldier::work() { // 訪問基類(人類)的m_strName m_strName = "JIm"; m_iAge = 20; cout << m_strName << endl; cout << m_iAge << endl; cout << "soldier - work" << endl; }
Infantry.h:
#include "Soldier.h" class Infantry:public Soldier { public: void attack(); };
Infantry.cpp:
#include <iostream> #include "Infantry.h" using namespace std; void Infantry::attack() { cout << "Infantry --attack" << endl; }
main.cpp:
#include "Soldier.h" #include <stdlib.h> #include <iostream> int main() { Soldier soldier; // work會間接的訪問到基類person中的m_strname soldier.work(); // 進行了公有繼承,父類的protected數據成員會被繼承到子類的protected下面 // 父類的public也會繼承到子類的public下面 soldier.play();//調用父類的成員函數 system("pause"); return 0; }
運行結果:
將soldier繼承Person的方式改成protected繼承:
class Soldier :protected Person { //略 };
若是slodier保護繼承了person,那麼person下的public和protected數據成員和成員函數都會被繼承到solider的protect部分。
這意味着經過子類對象只能直接訪問到本身的public下的成員函數與數據成員。
main.cpp:
int main() { Soldier soldier; soldier.work(); // work是本身public下的,正常訪問 soldier.play(); // play繼承過來被放在了protected,沒法直接訪問了。 system("pause"); return 0; }
報錯:
錯誤 C2247 「Person::play」不可訪問,由於「Soldier」使用「protected」從「Person」繼承
所以此時沒法直接訪問繼承過來的protected下的play(),只有兩種可能,一種根本沒有繼承過來,,一種是繼承過來沒法訪問。如何證實繼承過來了呢?
此時經過一個孫子類(Infantry)來公共繼承soldier則能夠在它自身的成員函數attack()中訪問到繼承來的Person的數據成員以及成員函數。
其實這時候在兒子類(Soldier)中新增一個成員函數,只要能使用protected中的父類數據成員和成員函數也是能夠證實的。
驗證方法1:(步兵類中attack()調用Person下數據成員)
Infantry.cpp:
#include <iostream> #include "Infantry.h" using namespace std; void Infantry::attack() { m_strName = "Mtianyan"; cout << m_strName << endl; cout << "Infantry --attack" << endl; }
main.cpp:
#include <stdlib.h> #include <iostream> #include "Infantry.h" int main() { Infantry infantry; infantry.attack(); system("pause"); return 0; }
能夠看到attack能夠訪問到繼承過來的protected的數據成員。
驗證方法2(不嚴謹),Soldier自身的成員函數來訪問Person的Protected數據成員
由於繼承到private,也能夠被自身成員函數訪問。
// Person.h protected: string m_strName; // Soldier.cpp(自身的work方法) void Soldier::work() { m_strName = "JIm"; cout << m_strName << endl; cout << "soldier - work" << endl; }
private繼承方式實驗
Solder.h:
class Soldier :private Person {};
Person中的public和protected都會被繼承到Soldier的private下。
這時在Soldier中能夠直接訪問這些數據成員。但孫子就拿不到爸爸從爺爺那私有繼承的數據成員了。
實驗證實:
錯誤 C2248 「Person::m_strName」了: 沒法訪問 沒法訪問的 成員(在「Person」類中聲明)
在孫子輩的步兵類中已經沒法訪問到爺爺的m_strName」了
mtianyan: B類從A類派生,那麼B類中含有A類的全部數據成員(私有的也被繼承過來了,只不過繼承到了不可見位置,沒法訪問)
B類從A類公共派生,那麼能夠在B類中直接使用A的公共及保護限定符的數據成員,不能使用私有成員。
B類從A類公共派生,那麼A類的私有成員函數不能被B類繼承並使用。(會繼承下來,可是不能使用,因此正確)
B類從A類私有派生,那麼A類的公共成員函數成爲B類的私有成員函數。
定義兩個類,人類中含有數據成員姓名(m_strName)及成員函數eat()
士兵類從人類派生,含有數據成員編號(m_strCode)及成員函數attack()
在main函數經過對數據的訪問,體會公有繼承的語法特色。
#include <iostream> #include <stdlib.h> #include <string> using namespace std; /** * 定義人的類: Person * 數據成員姓名: m_strName * 成員函數: eat() */ class Person { public: string m_strName; void eat() { cout << "eat" << endl; } }; /** * 定義士兵類: Soldier * 士兵類公有繼承人類: public * 數據成員編號: m_strCode * 成員函數: attack() */ class Soldier:public Person { public: string m_strCode; void attack() { cout << "fire!!!" << endl; } }; int main(void) { // 建立Soldier對象 Soldier soldier; // 給對象屬性賦值 soldier.m_strName = "mtianyan"; soldier.m_strCode = "273"; // 打印對象屬性值 cout << soldier.m_strName << endl; cout << soldier.m_strCode << endl; // 調用對象方法 soldier.attack(); soldier.eat(); return 0; }
覆蓋 <-> 隱藏,很容易混淆.
本次課程重點介紹隱藏。
表現: 實例化B類對象時,只能直接訪問到b中的abc方法。
同名數據成員和成員函數都具備隱藏性質。
父子關係 & 成員同名 & 隱藏
代碼:
class Person { public: void play(); protected: string m_strName; }; // 父子關係 class Soldier:public Person { public: void play(); // 同名成員函數 void work(); protected: int m_iCode; }
調用示例代碼:
int main() { Soldier soldier; soldier.play(); //調用到soldier本身的play soldier.Person::play(); //能夠調用到父類人的play return 0; }
數據成員同名:
void Soldier::work() { code = 1234; Person::code = "5678";//訪問到的是父類的數據成員 }
能夠經過比較好的命名方法(m_strCode,m_iCode)是能夠避免重名的。
4-2-HideMemberFunctionVariable
程序代碼
Person.h
#include <string> using namespace std; class Person { public: Person(); void play(); protected: string m_strName; };
Person.cpp:
#include "Person.h" #include <iostream> using namespace std; Person::Person() { m_strName = "mtianyan"; } void Person::play() { cout << "person - play()" << endl; cout << m_strName << endl; }
Soldier.h
#include "Person.h" class Soldier:public Person { public: Soldier(); void play(); void work(); protected: };
Soldier.cpp
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier() {} void Soldier::play() { cout << "soldier - play()" << endl; } void Soldier::work() { cout << "soldier - work()" << endl; }
main.cpp:
#include <iostream> #include <stdlib.h> #include "Soldier.h" int main() { Soldier soldier; soldier.play(); soldier.work(); soldier.Person::play(); system("pause"); return 0; }
使用方法名直接調用的是soldier本身的。
""
在程序目錄下找<>
在程序默認庫查找 - 右鍵打開文檔能夠打開#include<string>
時,須要配套使用using namespace std;
纔可正常使用string,不然會出錯,也就是string類型也是在std命名空間的。若是想要打印Person的。則須要soldier.Person::play();
能夠調用到person下的play方法。
當前不只同名並且參數相同。若是參數不一樣,還會隱藏嗎?
是的即便參數不一樣,也不會造成重載,只是隱藏。
// 不修改 void Person::play() { cout << "person - play()" << endl; cout << m_strName << endl; } // Soldier.h添加參數 void play(int x); // Soldier.cpp中添加參數 void Soldier::play(int x) { cout << "soldier - play()" << endl; } // 調用時 int main() { Soldier soldier; soldier.play(7); // 調用Soldier類本身的有參play soldier.work(); soldier.play(); //雖然與父類的參數要求一致,但當前父類方法被隱藏,沒法調用。 //soldier.Person::play(); system("pause"); return 0; }
報錯:
error C2660: 「Soldier::play」: 函數不接受 0 個參數
說明即便參數不一樣。父類和子類的同名函數也不造成重載。而是隱藏。
給Soldier.h protected下添加一個參數m_strName,使得它擁有一個和Person同名的數據成員
#include "Person.h" class Soldier:public Person { public: Soldier(); void play(int x); void work(); protected: string m_strName; };
Soldier.cpp:
#include "Soldier.h" #include <iostream> using namespace std; Soldier::Soldier() { } // 添加兩行打印 void Soldier::play(int x) { cout << m_strName << endl;//打印出的是soldier下的 cout << Person::m_strName << endl;//打印父類的 cout << "soldier - play()" << endl; } // 添加兩行數據成員的訪問 void Soldier::work() { m_strName = "solider"; //只能賦值給soldier下的m_strname Person::m_strName = "Person"; cout << "soldier - work()" << endl; }
父子之間的同名數據成員。work中直接訪問,只能訪問到soldier下的m_strname。
#include <iostream> #include <stdlib.h> #include "Soldier.h" int main() { Soldier soldier; soldier.work(); soldier.play(7); soldier.Person::play(); system("pause"); return 0; }
能夠看到數據成員被隱藏。
隱形眼鏡也是眼鏡 - Is-a
基於上面的結論,示例程序
int main() { Soldier s1; Person p1 = s1; //用s1區實例化p1.這樣作在語法上是正確的。士兵也是人。 Person *p2 = &s1; //正確 s1 = p1; //人賦值給士兵,錯誤。 Soldier *s2 = &p1; //士兵指針指向人對象,錯誤 return 0; }
子類的對象能夠賦值給父類。也能夠用基類的指針指向派生類的對象。
將基類的指針或者引用做爲函數參數來使它能夠接收傳入的子類對象,而且也能夠傳入基類的對象。
fun1和fun2均可以傳入Person 或 Soldier的對象。
&p1
對象p1的地址。而fun2要求傳入的是對象p的引用, 因此能夠直接傳入。
Person *p = &soldier;
派生類對象初始化基類對象: Person p1 = soldier;
存儲結構:
子類中有父類繼承下來的數據成員.也有它自身的數據成員。
當經過子類初始化父類時,會將繼承下來的數據成員複製。其餘子類自有的截斷丟棄。
父類指針指向子類對象,父類也只能訪問到本身遺傳下去的,沒法訪問到子類自有的。
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中不寫,也會是一個虛析構函數
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傳入的是對象。因此會有臨時生成的對象。而後銷燬。
實例化兩個父類的順序與繼承時冒號後順序一致而與初始化列表順序無關。
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; }
基類不須要定義虛析構函數,虛析構函數是在父類指針指向子類對象的時候使用的。 這裏只是簡單的實例化子類對象而已,銷燬的時候會執行父類和子類的析構函數的
- 定義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; }
多繼承 & 多重繼承的煩惱
菱形繼承中既有多繼承也有多重繼承。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能夠傳入膚色給person virtual ~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。
公共繼承的類須要寫上,不是公共繼承的也最好寫上,由於將來可能會被重定義。
推薦寫文件的全稱大寫,可是其實這個是自定義的,只要能區分開其餘文件就能夠。
能夠正常經過編譯說明咱們用宏定義成功的解決了菱形問題的重定義報錯。
與上小節中其餘代碼相同。
main.cpp:
int main() { MigrantWorker *p = new MigrantWorker("merry", "200", "yellow"); delete p; p = NULL; system("pause"); return 0; }
能夠看到Person的構造函數執行了兩遍。
如今MigrantWorker存在兩份Person的成員,下面咱們來證實。
Worker::Worker(string code,string color):Person("Worker"+color) Farmer::Farmer(string name,string color):Person("Farmer"+color)
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兒童類,
#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; }
運行結果:
能夠看到多繼承中實例化順序,先聲明的哪一個先實例化哪一個,與初始化列表順序無關。