繼承和多態

繼承

publicprivateprotectedios

繼承方式和屬性

 

(1) 公有繼承(public)c++

 

公有繼承的特色是基類的公有成員和保護成員做爲派生類的成員時,它們都保持原有的狀態,而基類的私有成員仍然是私有的,不能被這個派生類的子類所訪問。程序員

 

(2)私有繼承(private)面試

 

私有繼承的特色是基類的公有成員和保護成員都做爲派生類的私有成員,而且不能被這個派生類的子類所訪問。設計模式

 

(3)保護繼承(protected)安全

 

保護繼承的特色是基類的全部公有成員和保護成員都成爲派生類的保護成員,而且只能被它的派生類成員函數或友元訪問,基類的私有成員仍然是私有的。數據結構

 

 

 

請看個人的另外一篇:  框架

https://blog.csdn.net/qq_38504396/article/details/78658824 ide

 

繼承的基本測試

// public 修飾的成員 變量,方法,在類的內部和外部均可以訪問
// protected 修飾的成員 變量,方法,在類的內部使用,在繼承的子類中使用,不能在類的外部使用
// private 修飾的成員 變量,方法, 只能在類的內部使用 不能再類的外部使用函數

// 1 preotected 修飾的成員 變量,函數,是爲了在家族中使用,爲了繼承
// 2 項目開發中 通常使用 public

//
// Created by lk on 18-6-11.
//

#include <iostream>

using namespace std;

class Parent1
{
public:
    void print()
    {
        cout << "a = " << a << "\t b = " << b << endl;
    }
    int a;
protected:
private:
    int b;

};

// class Child: private Parent
// class Child: protected Parent
// 通常都是public繼承
class Child1: public Parent1
{
public:
protected:
private:
    int c;
};
int main()
{
    Child1 c1;
    c1.a = 1;
    // c1.b = 2;
    cout << "Hello world!" << endl;
    return 0;
}
繼承最基本的用法
#include <iostream>

using namespace std;

class Parent {
public:
    int a;

    void print() {
        cout << "print T" << endl;
    }

protected:
    int b;
private:
    int c;

};

// 保護繼承
class Child24 : protected Parent {
public:
    void useVar() {
        a = 0; // ok
        b = 0; // ok
        // c = 0; err
    }

protected:

private:

};

int main() {
    Child24 c1;
    // c1.a = 10;  // 都是錯的由於繼承是protected
    // c1.b = 20;
    // c1.c =30;
    return 0;
}
protected繼承
#include <iostream>

using namespace std;

class Parent {
public:
    int a;

    void print() {
        cout << "print T" << endl;
    }

protected:
    int b;
private:
    int c;

};

// 私有繼承
class Child : private Parent {
public:
    void useVar() {
        a = 0; // ok
        b = 0; // ok
        // c = 0; err
    }
protected:

private:

};

int main() {
    Child c1;
    // c1.a = 10;  都是錯的由於繼承是private
    // c1.b = 20;
    // c1.c =30;
    return 0;
}
private繼承
#include <iostream>

using namespace std;

class Parent {
public:
    int a;

    void print() {
        cout << "print T" << endl;
    }

protected:
    int b;
private:
    int c;

};

/*
    三看原則
    c++中的繼承方式(public, protected, private)會影響子類對外的訪問屬性
    判斷一句話可否被訪問
    1> 看調用語句,是寫在子類的內部,仍是外部
    2> 看子類的繼承方式 (public, protected, private)
    3> 看父類的 訪問級別 (public, protected, private)
*/

// 公有繼承
class Child2 : public Parent {
public:
    void useVar() {
        a = 0; // 正確
        b = 0; // 正確
        // c = 0; // 錯誤
    }

protected:
private:
};

int main() {
    Parent p1, p2;
    p1.a = 10; // ok
    // p1.b = 100;  //err
    // p1.c = 1000; //err
    cout << "Hello world!" << endl;
    return 0;
}
public繼承

類型兼容性原則  重要

類型兼容規則是指在須要基類對象的任何地方,均可以使用公有派生類的對象來替代。
經過公有繼承,派生類獲得了基類中除構造函數、析構函數以外的全部成員。
這樣,公有派生類實際就具有了基類的全部功能,凡是基類能解決的問題,公有派生類均可以解決。
類型兼容規則中所指的替代包括如下狀況:
子類對象能夠看成父類對象使用
子類對象能夠直接賦值給父類對象
子類對象能夠直接初始化父類對象
父類指針能夠直接指向子類對象
父類引用能夠直接引用子類對象
在替代以後,派生類對象就能夠做爲基類的對象使用,可是隻能使用從基類繼承的成員。
類型兼容規則是多態性的重要基礎之一。
子類就是特殊的父類 (base *p = &child;)
#include <iostream>

using namespace std;

class Parent{
public:
    void printP() {
        cout << "我是父類" << endl;
    }

    Parent() {
        cout << "構造函數" << endl;
    }

    Parent(const Parent&rho) {
        cout << "拷貝構造函數" << endl;
    }

protected:
private:
    int a;

};

class Child : public Parent{
public:
    void printC() {
        cout << "我是子類" << endl;
    }

protected:
private:
    int b;

};

/*
    兼容規則中所指的替代 包括如下狀況
    子類對象能夠當作父類對象使用
    子類對象能夠直接賦值給父類對象
    子類對象能夠直接初始化父類對象

    父類指針能夠直接指向子類對象
    父類引用能夠直接引用子類對象

*/
// c++編譯器不會報錯
void howToPrint(Parent*base) {
    base->printP();  // 只能執行父類的成員(調用子類和父類都有的成員)
}

void howToPrint(Parent&base) {
    base.printP();  // 只能執行父類的成員
}

int main() {
    Parent p1;
    p1.printP();

    Child c1;
    c1.printC();
    c1.printP();

    // 賦值兼容性原則,第一層含義
    // 1-1  父類指針 (引用) 指向子類對象 重要
    Parent*p = NULL;
    p = &c1;
    p->printP();

    // 1-2指針作函數參數
    howToPrint(&p1);
    howToPrint(&c1);

    // 1-3引用作函數參數
    howToPrint(p1);
    howToPrint(c1);

    // 賦值兼容性原則,第二層含義
    // 可讓子類對象 初始化 父類對象
    // 子類對象是特殊的父類對象
    Parent p3 = c1;

    cout << "p1 = " << sizeof(p1) << "\t c1 = " << sizeof(c1) << endl;
    cout << "Hello world!" << endl;
    return 0;
}
示例

當兼容性原則遇到函數重寫(virtual 函數重寫,發生多態)

#include <iostream>

using namespace std;

class Parent {
public:
    Parent(int a) {
        this->a = a;
        //    cout <<"parent a = " << a << endl;
    }

    virtual void print() {
        cout << "Parent 打印 a = " << a << endl;
    }

protected:
private:
    int a;
};

class Child : public Parent {
public:
    Child(int b) : Parent(10) {
        this->b = b;
        //  cout <<"Child b = " << b << endl;
    }

    // 若是父類寫了 子類的virtual 可寫可不寫, 最好寫上
    // 子類和父類的名字同樣
    virtual void print() {
        cout << "Child 打印 b = " << b << endl;
    }

protected:
private:
    int b;
};

void howToPrint(Parent *base) {
    base->print();   // 一種調用語句,有多種表現形態
}

void howToPrint(Parent &base) {
    base.print();
}

int main() {
    // print()函數不寫virtual時
    Parent *base = NULL;
    Parent p1(20);
    Child c1(10);

    base = &p1;
    base->print();  // 執行父類的打印函數

    base = &c1;
    base->print();  // 執行誰的函數? 子類 // 面向對象新需求
    // 執行父類的函數
    cout << endl;
    {
        Parent &base2 = p1;
        base2.print();    // 父類

        Parent &base3 = c1;  // base3是c1的別名
        base3.print();       // 打印的仍是子類
    }
    cout << endl;
    {
        // 函數調用
        howToPrint(&p1);  // 調用父類
        howToPrint(&c1); // 調用子類

        cout << endl;

        howToPrint(p1);   // 調用父類
        howToPrint(c1);  // 調用子類
    }
    return 0;
}
兼容性原則,遇到函數重寫

 

繼承中的構造和析構

問題:如何初始化父類成員?父類與子類的構造函數有什麼關係
在子類對象構造時,須要調用父類構造函數對其繼承得來的成員進行初始化
在子類對象析構時,須要調用父類析構函數對其繼承得來的成員進行清理
繼承中構造和析構的順序
一、子類對象在建立時會首先調用父類的構造函數
二、父類構造函數執行結束後,執行子類的構造函數
三、當父類的構造函數有參數時,須要在子類的初始化列表中顯示調用
四、析構函數調用的前後順序與構造函數相反

就是: 構造函數: 由內到外
  : 析構函數: 由外到內
#include <iostream>
using namespace std;

// 結論
// 先調用父類構造函數,在調用子類構造函數
// 析構順序:先調用子類析構函數,在調用父類析構函數
class Parent
{
public:
    void printP(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
        cout << "\t我是父類" << endl;
    }

    Parent(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
        cout << "\t父類構造函數" << endl;
    }

    Parent(const Parent &rho)
    {
        cout << "\t拷貝構造函數"<< endl;
    }

    ~Parent()
    {
        cout << "\t父類析構函數" << endl;
    }
protected:
private:
    int a;
    int b;

};
class Child: public Parent
{
public:
    void printC ()
    {
        cout << "\t我是子類" << endl;
    }
    Child(int a, int b,int c): Parent(a, b)
    {
        this->c = c;
        cout << "\t子類構造函數" << endl;
    }
    ~Child()
    {
        cout << "\t子類析構函數" << endl;
    }
protected:
private:
    int c;

};

void playObj()
{
    Parent p1(1, 2);
    Child c1(3, 4, 5);
}
int main()
{
    playObj();
    return 0;
}
//父類構造函數
//父類構造函數
//子類構造函數
//子類析構函數
//父類析構函數
//父類析構函數
構造和析構的順序測試
#include <iostream>

using namespace std;

// 這個程序不能調試
// 構造順序:先調用父類對象,若是父類還有父類(老祖宗) 先調用老祖宗構造函數, 而後父類,最後子類
// 析構函數:先析構子類,而後父類,最後老祖宗

class Object {
public:
    Object(int a, int b) {
        this->a = a;
        this->b = b;
        cout << "\tObject構造函數 a = " << a << "\t b = " << b << endl;
    }

    ~Object() {
        cout << "\tObject析構函數" << endl;
    }

protected:
    int a;
    int b;
private:

};

class Parent : public Object {
public:


    Parent(char *p) : Object(1, 2) {
        this->m_p = p;
        cout << "\tParent構造函數:p " << p << endl;
    }

    ~Parent() {
        cout << "\tParent析構函數" << endl;
    }

    void printP() {
        cout << "\t我是父類" << endl;
    }

protected:
    char *m_p;
private:

};

class Child : public Parent {
public:
    // 構造函數調用順序
    Child(char *p) : Parent(p), obj1(3, 4), obj2(5, 6) {
        this->myp = p;
        cout << "\tChild構造函數 myp " << myp << endl;
    }

    ~Child() {
        cout << "\tChild析構函數" << endl;
    }

    void printC() {
        cout << "\t我是子類" << endl;
    }

protected:
    char *myp;
    Object obj1;
    Object obj2;
private:

};

void play() {
    Child c1("繼承測試");
}

int main() {
    play();
    cout << "\tHello world!" << endl;
    return 0;
}
繼承組合混搭,有點小迷
//調用順序:Parent(p)構造時一看是繼承來的,向上找到parent,parent一看本身還有其餘的是繼承的,因此向上找
//而後找到object沒了(調用第一次a=1..),而後回到parent那調用char*構造,
//parent一看本身還有繼承的構造,而後obj1(3,4)和(5,6),
//調用完成後返回到child類完成構造函數
//開始析構函數

//Object構造函數 a = 1     b = 2
//Parent構造函數:p 繼承測試
//Object構造函數 a = 3     b = 4
//Object構造函數 a = 5     b = 6
//Child構造函數 myp 繼承測試
//        Child析構函數
//Object析構函數
//        Object析構函數
//Parent析構函數
//        Object析構函數
//Hello world!
繼承組合混帶結果

 

繼承中重名成員的調用方法

多繼承和虛繼承

// 多繼承的二義性:子類繼承父類,若是有多個父類,而且父類中相同的屬性,就會產生二義性
// 二義性解決方案:在繼承關係時加上virtual關鍵字: class b : virtual public b1, virtual b2;
// 可是解決不完全(多繼承始終存在問題)
// 多繼承在項目開發中通常不會用到
// class A{int a}; B : virtual A {int b};, C:public A {int c};;
// sizeof(A) = 4, B = 12, C = 8;
// 在虛繼承中 編譯器會偷偷的增長屬性

#include <iostream>
using namespace std;

class Base1
{
public:
    Base1(int b = 0)
    {
        b1 = b;
        cout << "B1有參構造函數" << endl;
    }
    void printB1()
    {
        cout << "b1 = " << b1 << endl;
    }
protected:
private:
    int b1;

};
class Base2
{
public:
    Base2(int b = 0)
    {
        b2 = b;
        cout << "B2有參構造函數" << endl;
    }
    void printB2()
    {
        cout << "b2 = " << b2 << endl;
    }
protected:
private:
    int b2;

};

class B: public Base1, public Base2
{
public:
    B(int b1, int b2, int c):Base1(b1),Base2(b2)
    {
        this->c = c;
        cout << "B的構造函數" << endl;
    }
    void printC()
    {
        cout << "c = " << c << endl;
    }
protected:
private:
    int c;

};
int main()
{
    B b1(1, 2, 3);
    b1.printB1();
    b1.printB2();
    b1.printC();

    return 0;
}
// 多繼承的二義性:子類繼承父類,若是有多個父類,而且父類中相同的屬性,就會產生二義性
// 二義性解決方案:在繼承關係時加上virtual關鍵字:  class b : virtual public b1, virtual b2;
// 可是解決不完全(多繼承始終存在問題)
// 多繼承在項目開發中通常不會用到
// class A{int a}; B : virtual A {int b};, C:public A {int c};;
// sizeof(A) = 4, B = 12, C = 8;
// 在虛繼承中 編譯器會偷偷的增長屬性
瞭解

 

繼承總結

繼承是面向對象程序設計實現軟件重用的重要方法。程序員能夠在已有基類的基礎上定義新的派生類。
單繼承的派生類只有一個基類。多繼承的派生類有多個基類。
派生類對基類成員的訪問由繼承方式和成員性質決定。
建立派生類對象時,先調用基類構造函數初始化派生類中的基類成員。調用析構函數的次序和調用構造函數的次序相反。
C++提供虛繼承機制,防止類繼承關係中成員訪問的二義性。
多繼承提供了軟件重用的強大功能,也增長了程序的複雜性。

多態

函數重載發生在同一個類中

在繼承中:

  注意:虛函數發生重寫(多態發生)必須有virtual關鍵字,

  非虛函數重寫(又叫重定義)(父類有一個函數, 可是子類想要從新定義這個函數 條件: 有繼承關係,函數名相同,不能有virtual關鍵字(有的話就是虛函數重寫了))

什麼是多態:

同種形態的不一樣表現形式

多態的分類:

靜態多態(靜態聯編)

動態多態(動態聯編)

靜態多態和動態多態的區別其實只是在何時將函數實現和函數調用關聯起來,是在編譯時期仍是運行時期,即函數地址是早綁定仍是晚綁定的? 
靜態多態是指在編譯期間就能夠肯定函數的調用地址,並生產代碼,這就是靜態的,也就是說地址是早早綁定的,靜態多態也每每被叫作靜態聯編。 
動態多態則是指函數調用的地址不能在編譯器期間肯定,必須須要在運行時才肯定,這就屬於晚綁定,動態多態也每每被叫作動態聯編。 

//父類中被重寫的函數依然會繼承給子類
//默認狀況下子類中重寫的函數將隱藏父類中的函數
//經過做用域分辨符::能夠訪問到父類中被隱藏的函數

https://blog.csdn.net/xy913741894/article/details/52939323

 

 

多態發生的條件:

必須有繼承,虛函數重寫, virtual關鍵字,  實參傳入指針或者引用(形參必須是父類指針)..搭建舞臺

總結來講:

1要有繼承
2要有虛函數重寫
3用父類指針(父類引用)指向子類對象....(搭建舞臺和調用)

多態的引出

#include <iostream>
// 我本身 想的,不必定對
using namespace std;

class Parent {
public:
    Parent(int a) {
        this->a = a;
    }

    void print() {
        cout << "Parent 打印 a = " << a << endl;
    }

protected:
private:
    int a;
};

class Child : public Parent {
public:
    Child(int b) : Parent(10) {
        this->b = b;
    }

    void print() {
        cout << "Child 打印 b = " << b << endl;
    }

protected:
private:
    int b;
};



int main() {
    Parent p1(10);
    Child c1(20);

    Parent *p2 = &c1;
    p2->print();    // 調用父類函數, 須要調用子類時 用做用域運算符來寫(麻煩) 引出多態
    return 0;
}
引出多態

多態的案例:

#include <iostream>
using namespace std;

// HeroFighter AdvHeroFighter EnemyFighter
class HeroFighter
{
public:
    virtual int power() // 看到virtual關鍵字 編譯器會對power特殊處理
    {
        return 10; // 本身的戰鬥機
    }
protected:
private:

};

class EnemyFighter
{
public:
    int attack() // 敵人的戰鬥機
    {
        return 15;
    }
protected:
private:

};

class AdvHeroFighter: public HeroFighter
{
public:
    virtual int power()
    {
        return 20;
    }
protected:
private:

};

class AdvAdvHeroFighter: public HeroFighter
{
public:
    virtual int power()
    {
        return 30;
    }
protected:
private:

};
// 多態 準換平臺,要想發生多態 注意最好是這裏接受基類的形參
// 而且 必須是 指針,或者 引用時 才能發生多態

//多態的威力
// 1 playObj給對象搭建舞臺 當作一個框架

void PlayObj(HeroFighter *const hf, EnemyFighter *const ef)
{
    // 若是這個power函數不寫virtual
    // 靜態聯編,c++編譯器根據HeroFight類型 去執行這個power函數,編譯階段就已經決定了函數的調用
    // 動態聯編 : 遲綁定 :在運行時根據具體的對象(類型) 執行不一樣對象的函數,表現成多態0
    if (hf->power() > ef->attack())  // hf->power()函數調用將會有多態發生
    {
        cout <<"主角win" << endl;
    }
    else
    {
        cout << "主角掛掉"<< endl;
    }
}
// 引用中使用多態
void PlayObj(HeroFighter &hf, EnemyFighter &ef)
{
    if (hf.power() > ef.attack())
    {
        cout <<"主角win" << endl;
    }
    else
    {
        cout << "主角掛掉"<< endl;
    }
}

// 面向對象的思想
// 封裝: 突破c函數的概念...用類作函數參數的時候,可使用對象的屬性 和對象的方法
// 繼承:A B 代碼的複用
// 多態:可使用將來...

// 多態很重要
// 多態發生的條件
// c語言 間接賦值 指針存在的最大意義
// 是c語言特有的現象(1:定義一個變量,2:創建關聯,3:*p在被調用函數中去間接修改函數的值)

// 實現多態的三個條件
// 1要有繼承
// 2要有虛函數重寫
// 3用父類指針(父類引用)指向子類對象....(搭建舞臺和調用)
int main()
{
    HeroFighter     hf;
    AdvHeroFighter  Adv_hf;
    EnemyFighter    ef;


    // 指針
    PlayObj(&hf, &ef); // 調用父類
    PlayObj(&Adv_hf, &ef); // 調用子類

    // 引用
    PlayObj(hf, ef); // 調用父類
    PlayObj(Adv_hf, ef); // 調用子類

    AdvAdvHeroFighter AdvAdv_hf;
    PlayObj(&AdvAdv_hf, &ef); // 這個框架能把 後來人寫的代碼,給調用起來
    return 0;
}

// 並無使用多態
int main1401()
{
    HeroFighter     hf;
    AdvHeroFighter  Adv_hf;
    EnemyFighter    ef;

    if (hf.power() > ef.attack())
    {
        cout <<"主角win" << endl;
    }
    else
    {
        cout << "主角掛掉"<< endl;
    }

    if (Adv_hf.power() > ef.attack())
    {
        cout <<"Adv主角win" << endl;
    }
    else
    {
        cout << "Adv主角掛掉"<< endl;
    }
    return 0;
}
多態的案例

比較大的例子,推薦看看

多態練習, 企業信息管理

https://files.cnblogs.com/files/xiaokang01/%E5%A4%9A%E6%80%81%E6%A1%88%E4%BE%8B-%E4%BC%81%E4%B8%9A%E5%91%98%E5%B7%A5%E4%BF%A1%E6%81%AF%E7%AE%A1%E7%90%86.zip

虛析構函數

#include <iostream>
#include <cstring>

using namespace std;

// 虛析構函數
class A {
public:
    A() {
        p = new char[20];
        strcpy(p, "obja");
        cout << "A()" << endl;
    }

    virtual ~A() {
        delete[]p;
        cout << "~A()" << endl;
    }

protected:
private:
    char *p;

};

class B : public A {
public:
    B() {
        p = new char[20];
        strcpy(p, "objb");
        cout << "B()" << endl;
    }

    virtual ~B() {
        delete[]p;
        cout << "~B()" << endl;
    }
protected:
private:
    char *p;

};

class C : public B {
public:
    C() {
        p = new char[20];
        strcpy(p, "objc");
        cout << "C()" << endl;
    }

    ~C() {
        delete[]p;
        cout << "~C()" << endl;
    }

protected:
private:
    char *p;

};
// 在這個場景下,只執行了父類的析構函數
// 面試題
// 想經過父類指針,把全部的子類對象的析構函數 都執行一邊
// 想父類指針,釋放全部子類資源


// 特別注意 在父類不加virtual關鍵字和加的區別
void howToDelete(A *base) {
    delete base; // 這句話不會表現爲多態,爲靜態
}

int main() {
    C *myC = new C; // new delete匹配


    // delete myC;
    // 直接經過子類對象釋放資源,不須要寫virtual,
    // 可是有時候不能這樣寫,因此仍是最好加virtual
    
    howToDelete(myC);
    return 0;
}
虛析構函數

重載重寫重定義

函數重載發生在同一個類中

在繼承中:

  注意:虛函數發生重寫(多態發生)必須有virtual關鍵字,

  非虛函數重寫(又叫重定義)(父類有一個函數, 可是子類想要從新定義這個函數 條件: 有繼承關係,函數名相同,不能有virtual關鍵字(有的話就是虛函數重寫了))

// 重載 重寫重定義
// 重寫發生在兩個類之間
// 重載必須在一個類之間

// 重寫分爲兩類
// 1虛函數重寫 將發生多態
// 2非虛函數重寫 (重定義)

#include <iostream>

using namespace std;

// 重載 重寫重定義
// 重寫發生在兩個類之間
// 重載必須在一個類之間

// 重寫分爲兩類
// 1虛函數重寫 將發生多態
// 2非虛函數重寫 (重定義)

class Parent {
    // 這三個函數都是重載關係
public:
    void abc() {
        cout << "Parent abc" << endl;
    }

    virtual void func() {
        cout << "func() do..." << endl;
    }

    virtual void func(int i) {
        cout << "func() do... " << i << endl;
    }

    virtual void func(int i, int j) {
        cout << "func() do..." << i << " " << j << endl;
    }

    virtual void func(int i, int j, int k, int f) {
        cout << "func()4個參數" << endl;
    }

protected:
private:

};

class Child : public Parent {
public:
    void abc() // abc函數將發生重定義,若是在父類中的abc前加一個virtual則是虛函數重寫(會發生多態)
    {
        cout << "Child abc" << endl;
    }

    void abc(int i) // 發生重載 和上面的abc無參數的
    {
        cout << "abc()" << i << endl;
    }

    virtual void func(int i, int j)   // 虛函重寫
    {
        cout << "func(int i, int j) do..." << i << " " << j << endl;
    }

    virtual void func(int i, int j, int k)  //
    {
        cout << "func(int i, int j, int k) do..." << endl;
    }

protected:
private:

};

// 重載重寫和重定義
int main() {
    Child c1;
    // c1.func(); // 不能用的緣由是:雖然子類繼承了父類的func(),
    // 可是子類的func(int i, int j)將無參的進行了覆蓋,要想調用必須顯示調用

    // 子類沒法重載父類的函數,父類同名函數將被名稱覆蓋
    //c1.func() 若是想用
    c1.Parent::func();

    // 1 c++編譯器看到func名字,因子類中func名字已經存在(名稱覆蓋),因此不會再去父類找4個參數的func了
    // 2c++編譯器只會在子類裏查找func函數,找到了兩個,可是都沒有4個參數的因此報錯
    // 若想調用父類的4個參數的函數,則指出做用域
    // c1.func(1,2,3,4);


    // func函數的名字,在子類中發生了名稱覆蓋,子類的函數的名字,佔用了父類的函數的名字的位置
    // 由於子類的中已經有了func函數的名字的重載形式。。。
    // 編譯器開始在子類中找func函數。。。可是沒有0個參數的func函數
    return 0;
}
重載,重寫(虛函數和非虛函數)

 

多態理論基礎

01靜態聯編和動態聯編
一、聯編是指一個程序模塊、代碼之間互相關聯的過程。
二、靜態聯編(static binding),是程序的匹配、鏈接在編譯階段實現,也稱爲早期匹配。
重載函數使用靜態聯編。
三、動態聯編是指程序聯編推遲到運行時進行,因此又稱爲晚期聯編(遲綁定)。
switch 語句和 if 語句是動態聯編的例子。
四、理論聯繫實際
一、C++與C相同,是靜態編譯型語言
二、在編譯時,編譯器自動根據指針的類型判斷指向的是一個什麼樣的對象;因此編譯器認爲父類指針指向的是父類對象。
三、因爲程序沒有運行,因此不可能知道父類指針指向的具體是父類對象仍是子類對象
從程序安全的角度,編譯器假設父類指針只指向父類對象,所以編譯的結果爲調用父類的成員函數。這種特性就是靜態聯編。

多態實現的原理探究

 
#include <iostream>

using namespace std;

// 多態發生的條件
// 要有繼承,虛函數重寫,父類指針(引用)指向子類對象
class Parent
{
public:
    Parent(int a = 0)
    {
        this->a = a;

    }
    virtual void print()
    {
        cout <<"我是父類"<< endl;
    }
protected:
private:
    int a;
};

class Child : public Parent
{
public:
    Child(int b = 0, int a = 0):Parent(a)
    {
        this->b = b;

    }
    virtual void print()  // 1動手腳 virtual關鍵字 會特殊處理 // 虛函數表
    {
        cout <<"我是子類"<< endl;
    }
protected:
private:
    int b;
};

void playObj(Parent * base)
{
    base->print();  // 這裏會發生多態 2 動手腳
    // 效果:傳來子類對象,執行子類的print函數,傳來父類對象,執行父類。。。
    // c++編譯器根本不須要區分是子類對象,仍是父類對象

    // 父類對象和子類對象都有vptr指針  ==>虛函數表 ==> 函數入口地址
    // 實現了:遲邦定(運行時,c++編譯器纔會判斷)
}
int main()
{
    Parent   p1;   // 3 提早佈局 動手腳
    // 用類定義對象的時候 c++編譯器,會在對象中添加vptr指針
    Child    c1;   // 子類對象也有一個vptr指針

    playObj(&p1);
    playObj(&c1);
    cout << "Hello world!" << endl;
    return 0;
}
多態原理探究

 

C++多態實實現的原理

    當類中聲明虛函數時,編譯器會在類中生成一個虛函數表
虛函數表是一個存儲類成員函數指針的數據結構
虛函數表是由編譯器自動生成與維護的
virtual成員函數會被編譯器放入虛函數表中
存在虛函數時,每一個對象中都有一個指向虛函數表的指針(vptr指針)

 

 

 

侯捷視頻 面向對象下的vptr和vtbl 17中講的

在一個繼承體系中每出現一個虛函數,在其結構中都會有一個vptr指針,該指針指向一個vtabl表

在vtable表中存放的是 虛函數, 具體調用那一個,看傳過來的是那一個類的指針(就是上圖的P)

證實vptr指針的存在

#include <iostream>
using namespace std;

class A
{
public:
    void printf()
    {
        cout<<"aaa"<<endl;
    }
protected:
private:
    int a;
};

class B
{
public:
    virtual void printf()
    {
        cout<<"aaa"<<endl;
    }
protected:
private:
    int a;
};
int main()
{
    //加上virtual關鍵字 c++編譯器會增長一個指向虛函數表的指針 。。。
    printf("sizeof(a):%d, sizeof(b):%d \n", sizeof(A), sizeof(B));
    cout<<"hello..."<<endl;
    return 0;
}
證實vptr的存在

在構造函數中調用虛函數,爲何不能實現多態

1)對象中的VPTR指針何時被初始化?
#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int a = 0)
    {
        this->a = a;
        print();
    }
    virtual void print()
    {
        cout <<"我是父類"<< endl;
    }
protected:
private:
    int a;
};

class Child : public Parent
{
public:
    Child(int b = 0, int a = 0):Parent(a)
    {
        this->b = b;

    }
    virtual void print()
    {
        cout <<"我是子類"<< endl;
    }
protected:
private:
    int b;
};

int main()
{
    Parent p1(1);  // 正常來講,構造函數會調用虛函數
    Child c1(1,2);  // 定義一個子類對象,在這個過程當中,在父類構造函數中調用虛函數print,能發生多態嗎,
    // 不能, 若是能的話,結果就會打印我是子類
//       c1.print();
    return 0;
}
多態的分佈初始化

對象在建立的時,由編譯器對VPTR指針進行初始化
只有當對象的構造徹底結束後VPTR的指向才最終肯定
父類對象的VPTR指向父類虛函數表
子類對象的VPTR指向子類虛函數表

關於多態的面試題

面試題1:請談談你對多態的理解

        多態的實現效果
多態:一樣的調用語句有多種不一樣的表現形態;
多態實現的三個條件
有繼承、有virtual重寫、有父類指針(引用)指向子類對象。
多態的C++實現
virtual關鍵字,告訴編譯器這個函數要支持多態;不是根據指針類型判斷如何調用;而是要根據指針所指向的實際對象類型來判斷如何調用
多態的理論基礎
動態聯編PK靜態聯編。根據實際的對象類型來判斷重寫函數的調用。
多態的重要意義
設計模式的基礎 是框架的基石。
實現多態的理論基礎
函數指針作函數參數
C函數指針是C++至高無上的榮耀。C函數指針通常有兩種用法(正、反)。
多態原理探究
與面試官展開討論
 

面試題2:談談c++編譯器是如何實現多態的

        c++編譯器多態實現原理

面試題3:談談你對重寫,重載的理解

        函數重載
必須在同一個類中進行
子類沒法重載父類的函數,父類同名函數將被名稱覆蓋
重載是在編譯期間根據參數類型和個數決定函數調用
函數重寫
必須發生於父類與子類之間
而且父類與子類中的函數必須有徹底相同的原型
使用virtual聲明以後可以產生多態(若是不使用virtual,那叫重定義)
多態是在運行期間根據具體對象的類型決定函數調用
#include <cstdlib>
#include <iostream>

using namespace std;

class Parent01
{
public:
    Parent01()
    {
        cout<<"Parent01:printf()..do"<<endl;
    }
public:
    virtual void func()
    {
        cout<<"Parent01:void func()"<<endl;
    }

    virtual void func(int i)
    {
        cout<<"Parent:void func(int i)"<<endl;
    }

    virtual void func(int i, int j)
    {
        cout<<"Parent:void func(int i, int j)"<<endl;
    }
};

class Child01 : public Parent01
{

public:

    //此處2個參數,和子類func函數是什麼關係
    void func(int i, int j)
    {
        cout<<"Child:void func(int i, int j)"<<" "<<i + j<<endl;
    }

    //此處3個參數的,和子類func函數是什麼關係
    void func(int i, int j, int k)
    {
        cout<<"Child:void func(int i, int j, int k)"<<" "<<i + j + k<<endl;
    }
};

void run01(Parent01* p)
{
    p->func(1, 2);
}

int main()
{
    Parent01 p;

    p.func();
    p.func(1);
    p.func(1, 2);

    Child01 c;
    //c.func(); //這個函數調用能運行嗎? 爲何
    c.Parent01::func();
    c.func(1, 2);

    run01(&p);
    run01(&c);

    return 0;
}
// 不能,由於在這裏發生的函數名稱覆蓋 在子類中又找不到無參的func,
// 想要調用必須指明調用的類名    c.Parent01::func();
View Code
//問題1:child對象繼承父類對象的func,請問這句話能運行嗎?why
//c.func(); //由於名稱覆蓋,C++編譯器不會去父類中尋找0個參數的func函數,只會在子類中找func函數。

//1子類裏面的func沒法重載父類裏面的func 
//2當父類和子類有相同的函數名、變量名出現,發生名稱覆蓋(子類的函數名,覆蓋了父類的函數名。)
//3//c.Parent::func();
//問題2 子類的兩個func和父類裏的三個func函數是什麼關係?
面試題4:是否可類的每一個成員函數都聲明爲虛函數,爲何。          
c++編譯器多態實現原理
面試題5:構造函數中調用虛函數能實現多態嗎?爲何?
c++編譯器多態實現原理
面試題6:虛函數表指針(VPTR)被編譯器初始化的過程,你是如何理解的?
c++編譯器多態實現原理
面試題7:父類的構造函數中調用虛函數,能發生多態嗎?
c++編譯器多態實現原理

面試題8:爲何要定義虛析構函數

在什麼狀況下應當聲明虛函數
構造函數不能是虛函數。創建一個派生類對象時,必須從類層次的根開始,沿着繼承路徑逐個調用基類的構造函數
析構函數能夠是虛的。虛析構函數用於指引 delete 運算符正確析構動態對象

 

 

 

父類指針和子類指針的步長

這個會報錯, 

#include <iostream>

using namespace std;

class Parent
{
public:
    Parent(int a = 0)
    {
        this->a = a;
//        this->c = c;
    }
    virtual void print()
    {
        cout <<"我是父類"<< endl;
    }
protected:
private:
    int a;

};

class Child : public Parent
{
public:
    Child(double b = 0, int a = 0):Parent(a)
    {
         this->b = b;
    }
    virtual void print()
    {
        cout <<"我是子類"<<  endl;
    }
protected:
private:
       double b;   // 若是將b換成int不會報錯.....,感受int也會報錯,
    // 由於兩個類大小不能,因此不能在用子類對象初始化的父類指針++,按理說會報錯
};
// 結論
// 多態的發生是,父類指針指向子類對象 和父類指針++,是兩個不一樣概念
// 這裏若是在子類加上b屬性,兩個類的大小不一樣,因此指針++就會報錯
int main()
{
    Parent p1(1);  // 正常來講,構造函數會調用虛函數
    Child c1(1,2);  // 定義一個子類對象,在這個過程當中,在父類構造函數中調用虛函數print,能發生多態嗎, 不能
    Parent *pP = NULL;
    Child *cC = NULL;
    Parent p;
    Child  c;
    cout << sizeof(p) <<"  " <<sizeof(c) << endl;

    Child arr[] = { Child(1), Child(2), Child(3) };
    pP = arr;
    cC = arr;

    pP->print();
    cC->print(); // 發生多態

    pP++;
    cC++;
    pP->print();
    cC->print(); // 發生多態

    pP++;
    cC++;
    pP->print();
    cC->print(); // 發生多態


    return 0;
}
探究步長

 

多態內存圖:

 

1)鐵律1:指針也只一種數據類型,C++類對象的指針p++/--,仍然可用。
2)指針運算是按照指針所指的類型進行的。
p++《=》p=p+1 //p = (unsigned int)basep + sizeof(*p) 步長。
結論:父類p++與子類p++步長不一樣;不要混搭,不要用父類指針++方式操做

相關文章
相關標籤/搜索