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

C++遠征之繼承篇

開篇介紹

整個C++遠征計劃: 起航->離港->封裝->繼承ios

mark

  • 爲何要用繼承?
  • 爲何要有繼承?
  • 如何來定義基類 <----> 派生類?
  • 基類到派生類的三種繼承關係: 公有繼承,保護繼承,私有繼承
  • IS-a & Has a
  • 多重繼承 & 多繼承 & 虛繼承(解決多繼承中的一些問題)

爲何繼承?

現實生活中: 兒子繼承父親財產c++

mark

  • 生活中繼承 不等於 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; // 會發工資
}

代碼重用,前提條件是發生關係的這兩個類是超集子集的關係。.net

mark

class Worker: public Person
{
public:
    void work();
    int m_iSalary;
}
此時工人類是人類的派生類。人類是工人類的基類。只須要寫本身特有的。

mark

這裏有一個配對的概念,提到基類的時候,對應的是派生類。提到父類時,與之對應的是子類。

內存中的對象:3d

mark

人類若是實例化一個對象,就有兩個數據成員。雖然在工人類定義姓名年齡,但由於繼承,工人類也包含姓名和年齡。

代碼演示

要求:指針

  • 實例化時先實例化子類,再實例化父類; 而析構函數正好相反。
  • 子類已經同時具備父類的數據成員和成員函數

要求

2-2-InheritWorkerPersoncode

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;
}

mark

能夠看到實例化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;
}

mark

棧中申請內存,也是先有爸爸,而後生兒子。 銷燬時,兒子先走,爸爸後走。

C++繼承方式

  • 公有繼承: 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在繼承時的區別:

  • 子類經過protect繼承了父類的時候,子類能夠經過本身來訪問父類的數據成員。
  • 子類經過private繼承了父類的時候,子類不能經過本身來訪問父類的數據成員,父類的數據成員只有經過本身才能訪問。

將剛纔的例子進行修改:

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

初始化時代碼與上次示例代碼保持一致,下面開始進行修改。

  1. 將堆中實例化的對象改用棧中實例化方式。

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;
}

mark

能夠看到運行結果沒有發生變化,依然能夠從子類調用父類的成員函數,數據成員。
  • 實驗二: 修改Person.h中數據成員
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;
}

mark

結果:正常運行。

  • 父類的private數據成員父類是否能夠正常訪問:不能夠

將上面代碼中person下的protected改成private

則會報錯:

Person::m_iAge」: 沒法訪問 private 成員(在「Person」類中聲明)

c++保護繼承,私有繼承

  • 公有繼承: 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;
}
在私有繼承中,子類也只能訪問父類的公有數據成員和公有成員函數。
  • 線段和座標的關係: has-a(包含關係)
  • 私有繼承(包含)
由於子類對象包含父類: 只能訪問父類當中公有數據成員和成員函數。

保護與私有繼承代碼演示

要求:

要求

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;
}

運行結果:

mark

將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;
}

mark

能夠看到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;
}

mark

c++隱藏

覆蓋 <-> 隱藏,很容易混淆.

本次課程重點介紹隱藏。

什麼是隱藏?

隱藏

  • 父子兩代(子類公有繼承父類),當都有同名函數。子類會將父類的函數隱藏。
  • 表現: 實例化B類對象時,只能直接訪問到b中的abc方法。
  • 可是實際上父類的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;
}

mark

使用方法名直接調用的是soldier本身的。
  • ""在程序目錄下找
  • <>在程序默認庫查找 - 右鍵打開文檔能夠打開
  • 使用string類型必須#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;
}

mark

能夠看到數據成員被隱藏。

isA

隱形眼鏡也是眼鏡 - 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的對象。
  • fun1要求傳入的參數是指針,因此傳入 &p1對象p1的地址。
  • 而fun2要求傳入的是對象p的引用, 因此能夠直接傳入。
  • 基類指針指向派生類對象: Person *p = &soldier;
  • 派生類對象初始化基類對象: Person p1 = soldier;

存儲結構:

子類父類內存

子類中有父類繼承下來的數據成員.也有它自身的數據成員。

當經過子類初始化父類時,會將繼承下來的數據成員複製。其餘子類自有的截斷丟棄。

父類指針指向子類對象,父類也只能訪問到本身遺傳下去的,沒法訪問到子類自有的。

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;
}

mark

能夠看到此時調用Person的play(),打印出的m_strName是Solder的數據成員。
int main()
{
    Soldier soldier;
    Person p; 

    p.play();

    system("pause");
    return 0;
}

mark

由於Person是有默認構造函數的,所以此時打印出來的一定彷佛Person中數據成員。

剛纔咱們是使用soldier來初始化p,如今咱們嘗試使用賦值方式。

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

mark

不管是初始化,仍是賦值,均可以實現。下面咱們試試指針方式。
int main()
{
    Soldier soldier;
    Person *p;
    p = &soldier;
    p->play();
    system("pause");
    return 0;
}

mark

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

p->work();

會提示錯誤:

錯誤    C2039    「work」: 不是「Person」的成員
  • 關於銷燬時,析構函數是如何執行的。
int main()
{
    Person *p = new Soldier;

    p->play();

    delete p;
    p=NULL;
    system("pause");
    return 0;
}

mark

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

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

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

若是沒有virtual,那麼在銷燬時會形成只執行了父類的析構函數,沒有執行子類的。

mark

新知識點: 虛析構函數

何時會用到:

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

這個關鍵字是會被繼承下去的,即便在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;
}

mark

運行結果前三行是實例化了Person和Soldier的輸出。
test1中調用了傳入的Person的play方法,打印處理Person_jim。
test1傳入s,調用了Person的play方法,數據成員是Soldier的。

銷燬了兩次Person,是由於調用時,有一個臨時的對象person

兩次析構函數

兩次析構函數是在兩個test1執行完以後自動執行的。由於此時傳入一個臨時變量p。用完就要銷燬掉。
test2(p);
    test2(s);

運行結果:

test2:引用中間沒有中間變量的銷燬

由於傳入的是引用。因此裏面調用的還是傳入的對象自己。沒有實例化臨時變量。
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;
}

mark

多繼承與多重繼承

多重繼承:

多重繼承

多重繼承-Is-a關係

上述關係具體到代碼上能夠這樣寫:

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

多繼承:

多繼承-一個兒子有兩個爸爸

多繼承-isa-可是農民和工人沒什麼關係

具體到代碼層面,以下:

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;
}

mark

能夠看到,如咱們預期的,先生爺爺,再生爸爸,最後生兒子。

步兵 IsA 軍人 IsA 人類

具備傳遞關係。

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

mark

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;
}

mark

  • 能夠用指向子類的指針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;
}

mark

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能夠傳入膚色給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。

公共繼承的類須要寫上,不是公共繼承的也最好寫上,由於將來可能會被重定義。

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

能夠正常經過編譯說明咱們用宏定義成功的解決了菱形問題的重定義報錯。

c++虛繼承(編碼二)

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

main.cpp:

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

mark

能夠看到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;
}

mark

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

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

Worker.h & Farmer.h中修改

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

mark

能夠看到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;
}

運行結果:

mark

能夠看到多繼承中實例化順序,先聲明的哪一個先實例化哪一個,與初始化列表順序無關。
相關文章
相關標籤/搜索