最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++ 超全面講解多態(tài)

 更新時間:2022年04月15日 10:21:45   作者:m0_52012656  
這篇文章主要介紹了C++多態(tài)的原理與實現(xiàn),多態(tài)是一種面向?qū)ο蟮脑O(shè)計思路,本身和C++不是強綁定的,其他語言當中一樣有多態(tài),只不過實現(xiàn)的方式可能有所不同。下面來一起了解更多詳細內(nèi)容吧

多態(tài)的概念

概念:通俗的來說就是多種形態(tài),具體就是去完成某個行為,當不同類型的對象去完成同一件事時,產(chǎn)生的動作是不一樣的,結(jié)果也是不一樣的。

舉一個現(xiàn)實中的例子:買票這個行為,當普通人買票時是全價;學生是半價;軍人是不需要排隊。

多態(tài)也分為兩種:

  • 靜態(tài)的多態(tài):函數(shù)調(diào)用
  • 動態(tài)的多態(tài):父類指針或引用調(diào)用重寫虛函數(shù)。

這里的靜態(tài)是指在編譯時實現(xiàn)多態(tài)的,而動態(tài)是在運行時完成的。

多態(tài)的定義及實現(xiàn)

構(gòu)成條件

多態(tài)一定是建立在繼承上的,那么除了繼承還要兩個條件:

  • 必須通過基類(父類)的指針或引用調(diào)用函數(shù)
  • 被調(diào)用的函數(shù)必須是虛函數(shù),且派生類(子類)必須對積累的虛函數(shù)進行重寫。

虛函數(shù)

概念:被virtual修飾的類成員函數(shù)稱為虛函數(shù)

class Person
{
public:
    virtual void BuyTicket()
    {
        cout<<"全價票"<<endl;
    }
};

注意:

  • 只有類的非靜態(tài)成員函數(shù)可以是虛函數(shù)
  • 虛函數(shù)這里virtual和虛繼承中用的是同一個關(guān)鍵字,但是他們之間沒有關(guān)系;虛函數(shù)這里是為了實現(xiàn)多態(tài);虛繼承是為了解決菱形繼承的數(shù)據(jù)冗余和二義性,它們沒有關(guān)聯(lián)

虛函數(shù)的重寫

概念:派生類(子類)中有一個跟基類(父類)完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值類型,函數(shù)名字,參數(shù)列表完全相同),稱子類的虛函數(shù)重寫了基類的虛函數(shù)。

例:

class Person
{
public:
    virtual void BuyTicket()
    {
        cout<<"全價票"<<endl;
    }
};
?
class Student :public Person
{
public:
    //子類的虛函數(shù)重寫了父類的虛函數(shù)
    virtual void BuyTicket()
    {
        cout<<"半價票"<<endl;
    }
};
?
class Soldier : public Person
{
public:
    //子類的虛函數(shù)重寫了父類的虛函數(shù)
    virtual void BuyTicket()
    {
        cout<<"優(yōu)先買票"<<endl;
    }
};
//多態(tài)的實現(xiàn)
void f(Person& p)//這塊的參數(shù)必須是引用或者指針
{
    p.BuyTicket();
}
?
int main()
{
    Person p;
    Student st;
    Soldier so;
    
    f(p);
    f(st);
    f(so);
    
    return 0;
}

注意:這里子函數(shù)的虛函數(shù)可以不加virtual,也算完成了重寫,但是父類的虛函數(shù)必須要加,因為子類是先繼承父類的虛函數(shù),繼承下來后就有了virtual屬性了,子類只是重寫這個virtual函數(shù);除了這個原因之外,還有一個原因,如果父類的析構(gòu)函數(shù)加了virtual,子類加不加都一定完成了重寫,就保證了delete時一定能實現(xiàn)多態(tài)的正確調(diào)用析構(gòu)函數(shù)。

虛函數(shù)重寫的兩個例外

1、協(xié)變

概念:派生類重寫基類虛函數(shù)時,與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對象的指針或者引用,派生類虛函數(shù)返回派生類對象的指針或者引用時,稱為協(xié)變

例:

class A{};
class B : public A{};
?
class Person
{
public:
    virtual A* f()
    {
        return new A;
    }
};
?
class Student : public Person
{
public:
    virtual B* f()           //返回值不同但是構(gòu)成虛函數(shù)重寫
    {
        return new B;
    }
};

2、析構(gòu)函數(shù)的重寫

如果基類的析構(gòu)函數(shù)為虛函數(shù),此時派生類析構(gòu)函數(shù)只要定義,無論是否加virtual關(guān)鍵字,都與基類的析構(gòu)函數(shù)構(gòu)成重寫,雖然基類與派生類析構(gòu)函數(shù)名字不同。雖然函數(shù)名不相同,看起來違背了重寫的規(guī)則,其實不然,這里可以理解為編譯器對析構(gòu)函數(shù)的名稱做了特殊處理,編譯后析構(gòu)函數(shù)的名稱統(tǒng)一處理成destructor

例:

class Person {
public:
    //建議把父類析構(gòu)函數(shù)定義為虛函數(shù),這樣方便子類的虛函數(shù)重寫父類的虛函數(shù)
    virtual ~Person() {cout << "~Person()" << endl;}
};
?
class Student : public Person {
public:
    virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生類Student的析構(gòu)函數(shù)重寫了Person的析構(gòu)函數(shù),下面的delete對象調(diào)用析構(gòu)函數(shù),才能構(gòu)成多態(tài),才能保證p1和p2指向的對象正確的調(diào)用析構(gòu)函數(shù)。
int main()
{
    Person* p1 = new Person;
   //這里p2指向的子類對象,應該調(diào)用子類析構(gòu)函數(shù),如果沒有調(diào)用的話,就可能內(nèi)存泄漏
    Person* p2 = new Student;
    //多態(tài)行為
    delete p1;
    delete p2;
    //只有析構(gòu)函數(shù)重寫了那么這里delete父類指針調(diào)用析構(gòu)函數(shù)才能實現(xiàn)多態(tài)。
    return 0;
}

C++11 override和finel

從上面可以看出,C++對函數(shù)重寫的要求比較嚴格,但是有些情況下由于疏忽,可能會導致函數(shù)名字母次序?qū)懛炊鵁o法構(gòu)成重載,而這種錯誤在編譯期間是不會報出的,只有在程序運行時沒有得到預期結(jié)果才來debug會得不償失,因此:C++11提供了override和final兩個關(guān)鍵字,可以幫助用戶檢測是否重寫

final:修飾虛函數(shù),表示該虛函數(shù)不能再被重寫

class Car
{
public:
    virtual void Drive() final {}
};
class Benz :public Car
{
public:
    //會在這塊報錯,因為基類的虛函數(shù)已經(jīng)被final修飾,不能被重寫了
    virtual void Drive() {cout << "Benz-舒適" << endl;}
};  

override: 檢查派生類虛函數(shù)是否重寫了基類某個虛函數(shù),如果沒有重寫編譯報錯

class Car{
public:
    virtual void Drive(){}
};
class Benz :public Car {
public:
    virtual void Drive() override {cout << "Benz-舒適" << endl;}
};  

重載、覆蓋(重寫)、隱藏(重定義)的對比

抽象類

抽象類的概念

純虛函數(shù):在虛函數(shù)的后面加上=0就是純虛函數(shù),有純虛函數(shù)的類就是抽象類,也叫接口類,抽象類無法實例化對象。抽象類的子類不重寫父類的虛函數(shù)的話,也是一個抽象類。

//抽象類的定義
class Car
{
public:
    virtual void run()=0;   //不用實現(xiàn)只寫接口就行。   
};

純虛函數(shù)不寫函數(shù)體,并不意味著不能實現(xiàn),只是我們不寫。因為寫出來也沒有人用。

虛函數(shù)的作用

  • 強制子類重寫虛函數(shù),完成多態(tài)。
  • 表示抽象類。

接口繼承和實現(xiàn)繼承

普通函數(shù)的繼承就是實現(xiàn)繼承,虛函數(shù)的繼承就是接口繼承。子類繼承了函數(shù)的實現(xiàn),可以直接使用。虛函數(shù)重寫后只會繼承接口,重寫實現(xiàn)。所以如果不用多態(tài),就不要把函數(shù)寫為虛函數(shù)。

純虛函數(shù)就體現(xiàn)了接口函數(shù)。下面我們來實現(xiàn)一道題,展現(xiàn)一下接口繼承。

class A
{
public:
    virtual void fun(int val=0) 
    {
        cout<<"A->val = "<<val <<endl;
    }
    void Fun()
    {
        fun();
    }
};
?
class B:public A
{
public:
    virtual void fun(int val=1)
    {
        cout<<"B->val"<<val<<endl;
    }
};
?
int main()
{
    B b;
    A* a=&b;
    a->Fun();
    return 0;
}

結(jié)果打印為 :B->val=0

子類對象切片給父類指針,傳給Fun函數(shù),滿足多態(tài),會去調(diào)用子類的fun函數(shù),但是子類的虛函數(shù)繼承了父類的接口,所以val是父類的0。

多態(tài)的原理

虛函數(shù)表

class A
{
public:
    virtual void fun()
    {
        
    }
    protected:
    int _a;
};

sizeof(A)是多少?

打印出來是8。

我們定義了一個A類型的對象a,打開調(diào)試窗口,發(fā)現(xiàn)a的內(nèi)容如下

我們發(fā)現(xiàn)出了成員變量_a以外,還多了一個指針,這個指針是不準確的,實際上應該是 _vftptr(virtual function table pointer),虛函數(shù)表指針。在計算類大小的時候要加上這個指針的大小。虛表就是存放虛函數(shù)的地址地方,當我們?nèi)フ{(diào)用虛函數(shù),編譯器就會通過虛表指針去虛表里查找。

class A
{
public:
    void fun1()
    {
        
    }
    virtual void fun2()
    {}
};
?
int main()
{
    A* a=nullptr;
    a->fun1();//調(diào)用函數(shù),因為這是普通函數(shù)的調(diào)用
    a->fun2();//調(diào)用失敗,虛函數(shù)需要對指針操作,無法操作空指針。
    return 0;
}

實現(xiàn)一個繼承

class A
{
    public:
    virtual void fun1()
    {}
    virtual void fun2()
    {}
};
class B : public A
{
    public:
    virtual void fun1()
    {}
    virtual void fun2()
    {}
};
?
int main()
{
    A a;
    B b;
    return 0;
}

子類與父類一樣有一個虛表指針。

子類的虛函數(shù)表一部分繼承自父類。如果重寫了虛函數(shù),那么子類的虛函數(shù)會在虛表上覆蓋父類的虛函數(shù)。

本質(zhì)上虛函數(shù)表是一個虛函數(shù)指針數(shù)組,最后一個元素是nullptr,代表虛表的結(jié)束。所以,如果繼承了虛函數(shù),那么

  • 子類先拷貝一份父類虛表,然后用一個虛表指針指向這個虛表。
  • 如果有虛函數(shù)重寫,那么在子類的虛表上用子類的虛函數(shù)覆蓋。
  • 子類新增的虛函數(shù)按其在子類中的聲明次序增加到子類虛表的最后。

虛函數(shù)表放在內(nèi)存的那個區(qū),虛函數(shù)又放在哪?

虛函數(shù)與虛函數(shù)表都放在代碼段。

多態(tài)的原理

我們現(xiàn)在來看多態(tài)的原理

class person
{
public:
    virtual void fun()
    {
        cout<<"全價票"<<endl;
    }
};
class student : public person
{
public:
    virtual void fun()
    {
        cout<<"半價票"<<endl;
    }
};
void buyticket(person* p)
{
    p->fun();
}

這樣就實現(xiàn)了不同對象去調(diào)用同一函數(shù),展現(xiàn)出不同的形態(tài)。 滿足多態(tài)的函數(shù)調(diào)用是程序運行是去對象的虛表查找的,而虛表是在編譯時確定的。 普通函數(shù)的調(diào)用是編譯時就確定的。

動態(tài)綁定與靜態(tài)綁定

1.靜態(tài)綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態(tài)多態(tài),比如:函數(shù)重載

2.動態(tài)綁定又稱后期綁定(晚綁定),是在程序運行期間,根據(jù)具體拿到的類型確定程序的具體行為,調(diào)用具體的函數(shù),也稱為動態(tài)多態(tài)。我們說的多態(tài)一般是指動態(tài)多態(tài)。

這里我附上一個有意思的問題:

就是在子類已經(jīng)覆蓋了父類的虛函數(shù)的情況下,為什么子類還是可以調(diào)用“被覆蓋”的父類的虛函數(shù)呢?

#include <iostream>
using namespace std;
?
class Base {
public:
    virtual void func() {
        cout << "Base func\n";
    }
};
?
class Son : public Base {
public:
    void func() {
        Base::func();
        cout << "Son func\n";
    }
};
?
int main()
{
    Son b;
    b.func();
    return 0;
}

輸出:

Base func

Son func

這是C++提供的一個回避虛函數(shù)的機制

通過加作用域(正如你所嘗試的),使得函數(shù)在編譯時就綁定。

到此這篇關(guān)于C++ 超全面講解多態(tài)的文章就介紹到這了,更多相關(guān)C++ 多態(tài)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 7種排序算法的實現(xiàn)示例

    7種排序算法的實現(xiàn)示例

    這篇文章主要介紹了7種排序算法的實現(xiàn)示例,需要的朋友可以參考下
    2014-05-05
  • C語言實現(xiàn)字符轉(zhuǎn)unix時間戳的簡單實例

    C語言實現(xiàn)字符轉(zhuǎn)unix時間戳的簡單實例

    下面小編就為大家?guī)硪黄狢語言實現(xiàn)字符轉(zhuǎn)unix時間戳的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-06-06
  • QT6安裝圖文教程(兼容QT5、QT4)

    QT6安裝圖文教程(兼容QT5、QT4)

    本文主要介紹了QT6安裝圖文教程(兼容QT5、QT4),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-02-02
  • C++無鎖數(shù)據(jù)結(jié)構(gòu)實現(xiàn)示例詳解

    C++無鎖數(shù)據(jù)結(jié)構(gòu)實現(xiàn)示例詳解

    這篇文章主要為大家介紹了C++無鎖數(shù)據(jù)結(jié)構(gòu)實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • 安裝OpenMPI來配合C語言程序進行并行計算

    安裝OpenMPI來配合C語言程序進行并行計算

    這篇文章主要介紹了安裝OpenMPI來配合C語言程序進行并行計算的例子,MPI的全稱是Message Passing Interface即標準消息傳遞界面,可以用于并行計算,需要的朋友可以參考下
    2015-11-11
  • C語言實現(xiàn)的bitmap位圖代碼分享

    C語言實現(xiàn)的bitmap位圖代碼分享

    這篇文章主要介紹了C語言實現(xiàn)的bitmap位圖代碼分享,位圖(bitmap)是一種非常常用的結(jié)構(gòu),在索引、數(shù)據(jù)壓縮等方面有廣泛應用,需要的朋友可以參考下
    2014-08-08
  • C++實現(xiàn)簡易的五子棋游戲

    C++實現(xiàn)簡易的五子棋游戲

    這篇文章主要為大家詳細介紹了C++實現(xiàn)簡易的五子棋游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C++變量引用的概念介紹

    C++變量引用的概念介紹

    這篇文章主要介紹了C++變量引用的概念介紹,簡單提到了與指針概念的不同,通過代碼場景分析給大家介紹的非常詳細,需要的朋友可以參考下
    2021-08-08
  • C++深入探究不同的繼承體系

    C++深入探究不同的繼承體系

    繼承是C++面向?qū)ο缶幊讨械囊婚T。繼承是子類繼承父類的特征和行為,或者是繼承父類得方法,使的子類具有父類得的特性和行為。重寫是子類對父類的允許訪問的方法實行的過程進行重新編寫,返回值和形參都不能改變。就是對原本的父類進行重新編寫,但是外部接口不能被重寫
    2022-05-05
  • 如何在Qt中實現(xiàn)關(guān)于Json?的操作

    如何在Qt中實現(xiàn)關(guān)于Json?的操作

    JSON是一種輕量級數(shù)據(jù)交換格式,常用于客戶端和服務端的數(shù)據(jù)交互,不依賴于編程語言,在很多編程語言中都可以使用JSON,這篇文章主要介紹了在Qt中實現(xiàn)關(guān)于Json的操作,需要的朋友可以參考下
    2023-08-08

最新評論

昌图县| 观塘区| 大邑县| 凤山市| 沐川县| 金川县| 丽水市| 昌平区| 常德市| 静安区| 浑源县| 舞钢市| 容城县| 建水县| 文登市| 义马市| 珠海市| 江山市| 高青县| 阿鲁科尔沁旗| 怀来县| 剑阁县| 荣成市| 宁远县| 辉南县| 玉环县| 蓝田县| 广河县| 黑河市| 南涧| 桃江县| 邮箱| 乳山市| 上林县| 集安市| 太康县| 旬阳县| 彰化县| 宜兰县| 化德县| 观塘区|