多態(tài)與虛函數(shù)

多態(tài)的基本概念

實現(xiàn)了多態(tài)機制的程序铸抑,可以使用同一個名字完成不同的功能。

多態(tài)分為編譯時多態(tài)和運行時多態(tài)。

多態(tài)

靜態(tài)多態(tài)在編譯期間就可以確定函數(shù)的調(diào)用地址偿衰,并產(chǎn)生代碼。

函數(shù)調(diào)用與代碼入口地址的綁定需要在運行時刻才能確定改览,稱為動態(tài)聯(lián)編或動態(tài)綁定下翎。

在類之間滿足賦值兼容的前提下,實現(xiàn)動態(tài)綁定必須滿足以下兩個條件

  • 必須聲明虛函數(shù)
  • 通過基類類型的引用或者指針調(diào)用虛函數(shù)恃疯。

虛函數(shù)

所謂虛函數(shù)漏设,就是在函數(shù)聲明時前面加了virtual關鍵字的成員函數(shù)。
聲明虛函數(shù)成員的格式

virtual 函數(shù)返回值類型 函數(shù)名(形參表);

通過基類指針實現(xiàn)多態(tài)

程序6-1 通過基類指針實現(xiàn)多態(tài)示例

#include <iostream>
using namespace std;

class A
{
    public:
        virtual void Print()
        {
            cout<<"A::Print"<<endl;
        }
};

class B:public A
{
    public:
        virtual void Print()
        {
            cout<<"B::Print"<<endl;
        }
};

class D:public A
{
    public:
        virtual void Print()
        {
            cout<<"D::Print"<<endl;
        }
};

class E:public B
{
    public:
        virtual void Print()
        {
            cout<<"E::Print"<<endl;
        }
};

int main()
{
    A a; B b; D d; E e;
    A *pa = &a;             //基類指針pa指向基類對象a
    B *pb = &b;             //派生類指針pb指向派生類對象B
    pa->Print();        //多態(tài)今妄,目前指向基類對象郑口,調(diào)用a.Print(),輸出A::Print
    pa = pb;            //派生類指針賦給基類指針,pa指向派生類對象b
    pa->Print();        //多態(tài)盾鳞,目前指向派生類對象犬性,調(diào)用b.Print(),輸出B::Print
    pa = &d;            //基類指針pa指向派生類對象d
    pa->Print();        //多態(tài),目前指向派生類對象腾仅,調(diào)用d.Print(),輸出D::Print
    pa = &e;            //基類指針pa指向派生類對象e
    pa->Print();        //多態(tài)乒裆,目前指向派生類對象,調(diào)用e.Print(),輸出E::Print
    return 0; 
}

A::Print
B::Print
D::Print
E::Print

程序6-2 用基類指針訪問基類對象及派生類對象

#include <iostream>
#include <string>
using namespace std;
class A
{
    public:
        void put_name(string s)
        {
            name = s;
        }
        virtual void print_name() const
        {
            cout<<"A::"<<name<<"\n";
        }
        string name;
};

class B:public A
{
    public:
        void put_name(string s)
        {
            put_name(s);
        }
        virtual void print_name() const
        {
            cout<<"B::"<<name<<","<<A::name<<"\n"; 
        }
        void put_phone(string num)
        {
            phone_num = num;
        }
        void print_phone() const
        {
            cout<<phone_num<<"\n";
        }
        string phone_num;           //派生類中的成員變量 
};

int main()
{
    A *A_p;
    A A_obj;
    B B_obj;
    A_p = &A_obj;       //基類指針指向基類對象
    A_p->put_name("多態(tài)示例_名字");       //賦給基類對象A_obj
    cout<<"A_p->print_name的輸出內(nèi)容:\t";
    A_p->print_name();          //使用指針輸出推励,A_obj中的值
    cout<<"A_obj.print_name()的輸出內(nèi)容:\t";
    A_obj.print_name(); //使用對象輸出
    
    A_p = &B_obj;       //基類指針指向派生類對象 
    A_p->put_name("另一個名字");     //多態(tài)鹤耍,賦給派生類對象B_obj 
    cout<<"A_p->print_name()的輸出內(nèi)容:\t";
    A_p->print_name();      //多態(tài),使用指針輸出验辞,B_obj中的值稿黄,繼承的name的值 
    cout<<"B_obj.print_name()的輸出內(nèi)容:\t";
    B_obj.print_name();     //使用對象輸出,B_obj中的值跌造,繼承的name值 
    B_obj.put_phone("電話號碼999"); //派生類對象賦值 
    
    cout<<"((B *)A_p)->print_phone()的輸出內(nèi)容:\t";
    ((B*)A_p)->print_phone();   //強制轉(zhuǎn)換基類指針杆怕,輸出的是派生類對象中的值 
    cout<<"B_obj.print_phone()的輸出內(nèi)容:\t\t";
    B_obj.print_phone();    //輸出的是派生類對象中的值 
    return 0; 
}

A_p->print_name的輸出內(nèi)容:     A::多態(tài)示例_名字
A_obj.print_name()的輸出內(nèi)容:  A::多態(tài)示例_名字
A_p->print_name()的輸出內(nèi)容:   B::另一個名字,另一個名字
B_obj.print_name()的輸出內(nèi)容:  B::另一個名字,另一個名字
((B *)A_p)->print_phone()的輸出內(nèi)容:   電話號碼999
B_obj.print_phone()的輸出內(nèi)容:         電話號碼999

通過基類引用實現(xiàn)多態(tài)

通過基類指針調(diào)用虛函數(shù)時可以實現(xiàn)多態(tài),通過基類的引用調(diào)用虛函數(shù)的語句也是多態(tài)的壳贪。

程序6-3 基類引用實現(xiàn)多態(tài)

#include <iostream>
using namespace std;

class A
{
    public:
        virtual void Print()
        {
            cout<<"A::Print"<<endl;
        }
};

class B:public A
{
    public:
        virtual void Print()
        {
            cout<<"B::Print"<<endl;
        }
};

void PrintInfo(A &r)
{
    r.Print();      //多態(tài)陵珍,使用基類引用調(diào)用哪個Print()取決于r引用了哪個類的對象 
}

int main()
{
    A a;
    B b;
    PrintInfo(a);
    PrintInfo(b);
    return 0;
}

A::Print
B::Print

*多態(tài)的實現(xiàn)原理

多態(tài)的關鍵在于通過基類指針或引用調(diào)用一個虛函數(shù)時,編譯階段不能確定到底調(diào)用的時基類還是派生類的函數(shù)违施,運行時才能確定互纯。

程序6-4 多態(tài)機制下對象存儲空間的大小

#include <iostream>
using namespace std;

class A
{
    public:
        int i;
        virtual void func(){}
        virtual void func2(){}
};

class B:public A
{
    int j;
    void func(){}
};

int main()
{
    cout<<sizeof(A)<<","<<sizeof(B);
    return 0;
}

16,16

多態(tài)實例

在面向?qū)ο蟮某绦蛟O計中,使用多態(tài)能夠增強程序的可擴充性磕蒲。

使用多態(tài)也能起到精簡代碼的作用伟姐。

程序6-5 使用多態(tài)出來圖形示例

#include <iostream>
#include <cmath>
using namespace std;

class CShape        //基類:圖形類 
{
    protected:
        double acreage;     //圖形的面積收苏,子類可以訪問
    public:
        CShape()
        {
//          cout<<"基類構造函數(shù)"<<endl;   
        };
        virtual ~CShape()
        {
//          cout<<"基類析構函數(shù)"<<endl;   
        };
        virtual double CalAcr()         //計算面積,虛函數(shù) 
        {
            return 0;
        };
        virtual void setAcreage(double acre){};     //設置面積值愤兵,虛函數(shù)
        virtual void PrintInfo(){}; //顯示信息鹿霸,虛函數(shù)
};

class CRectangle:public CShape      //派生類:矩形類 
{
    double width,high;      //矩形的寬度和高度
    public:
        CRectangle(double w,double h)
        {
//          cout<<"矩形帶參構造函數(shù)"<<endl; 
            width = w;
            high = h;
        };
        CRectangle()
        {
//          cout<<"矩形無參構造函數(shù)"<<endl;
            width = 0;
            high = 0;
        };
        ~CRectangle()
        {
//          cout<<"矩形析構函數(shù)"<<endl;
        };
        virtual double CalAcr();    //繼承的函數(shù),虛函數(shù)
        virtual void PrintInfo();   //繼承的函數(shù)秆乳,虛函數(shù)
        virtual void setAcreage(double);    //繼承的函數(shù)懦鼠,虛函數(shù) 
};

class CCircle:public CShape     //派生類:圓形類 
{
    double radius;      //半徑,私有的
    public:
         CCircle(double r)
         {
//          cout<<"圓形帶參構造函數(shù)"<<endl;
            radius = r;
         };
         CCircle()
         {
//          cout<<"圓形無參構造函數(shù)"<<endl;
            radius = 0;
        };
         ~CCircle()
         {
//          cout<<"圓形析構函數(shù)"<<endl;
        };
        virtual double CalAcr();    //繼承的函數(shù)屹堰,虛函數(shù)
        virtual void PrintInfo();   //繼承的函數(shù)肛冶,虛函數(shù)
        virtual void setAcreage(double);    //繼承的函數(shù),虛函數(shù) 
};

class CTriangle:public CShape       //派生類:三角形類 
{
    double a,b,c;       //三條邊的長度扯键,私有的
    public:
        CTriangle(double a,double b,double c)
        {
//          cout<<"三角形帶參構造函數(shù)"<<endl;
            this->a = a;
            this->b = b;
            this->c = c;
        };
        CTriangle()
        {
//          cout<<"三角形無參構造函數(shù)"<<endl;
            a = 0;
            b = 0;
            c = 0;
        };
        ~CTriangle()
        {
//          cout<<"三角形析構函數(shù)"<<endl;
        };
        virtual double CalAcr();    //繼承的函數(shù)睦袖,虛函數(shù)
        virtual void PrintInfo();   //繼承的函數(shù),虛函數(shù)
        virtual void setAcreage(double);    //繼承的函數(shù)荣刑,虛函數(shù) 
};

double CRectangle::CalAcr()     //計算矩形面積 
{
    return width * high;
}

void CRectangle::PrintInfo()        //輸出矩形信息 
{
    cout<<"矩形馅笙。\t寬度="<<this->width<<",高度="<<this->high<<",面積="<<this->acreage<<endl; 
}

void CRectangle::setAcreage(double acre)        //設置面積值 
{
    acreage = acre;     //訪問基類保護成員 
}

double CCircle::CalAcr()        //計算圓形的面積 
{
    return 3.14*radius*radius;
}

void CCircle::PrintInfo()       //輸出圓形信息 
{
    cout<<"圓。\t半徑="<<this->radius<<",面積="<<this->acreage<<endl;
}

void CCircle::setAcreage(double acre)
{
    acreage = acre;
} 

double CTriangle::CalAcr()      //根據(jù)海倫公式計算三角形面積 
{
    double p = (a+b+c)/2.0;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}

void CTriangle::PrintInfo()
{
    cout<<"三角形厉亏。三條邊分別是:"<<this->a<<","<<this->b<<","<<this->c<<",面積="<<this->acreage<<endl;
}

void CTriangle::setAcreage(double acre)
{
    acreage = acre;
}

CShape *pShapes[100];           //用來存儲各種幾何形狀董习,最對100個
int main()
{
    int i,n;
    double temp1,temp2,temp3;
    CRectangle *pr;
    CCircle *pc;
    CTriangle *pt;
    cin>>n;
    for(i=0;i<n;++i)
    {
        char c;
        cin>>c;
        switch(c)
        {
            case 'R':case 'r':      //矩形
                cin>>temp1>>temp2;
                pr = new CRectangle(temp1,temp2);
                pr->setAcreage(pr->CalAcr());
                pShapes[i] = pr;
                break;
            case 'C':case 'c':
                cin>>temp1;         //圓形
                pc = new CCircle(temp1);
                pc->setAcreage(pc->CalAcr());
                pShapes[i] = pc;
                break;
            case 'T':case 't':      //三角形
                cin>>temp1>>temp2>>temp3;
                pt = new CTriangle(temp1,temp2,temp3);
                pt = new CTriangle(temp1,temp2,temp3);
                pt->setAcreage(pt->CalAcr());
                pShapes[i]= pt;
                break; 
        }   
    }   
    if(n == 1) cout<<"共有"<<n<<"種圖形,它是:"<<endl;
    else cout<<"共有"<<n<<"種圖形爱只,分別是:"<<endl;
    for(i=0;i<n;++i)
    {
        pShapes[i]->PrintInfo();
        delete pShapes[i];      //釋放空間 
    }
    return 0; 
} 

3
C 6
R 7.6 8.2
T 3 4 5
共有3種圖形皿淋,分別是:
圓。    半徑=6,面積=113.04
矩形恬试。  寬度=7.6,高度=8.2,面積=62.32
三角形窝趣。三條邊分別是:3,4,5,面積=6

多態(tài)的使用

類的成員函數(shù)直接按可以互相調(diào)用。

在普通成員函數(shù)(靜態(tài)成員函數(shù)训柴、構造函數(shù)和析構函數(shù)除外)中調(diào)用其他虛成員函數(shù)也是允許的哑舒,并且是多態(tài)的。
程序6-6 在成員函數(shù)中調(diào)用虛函數(shù)

#include <iostream>
using namespace std;
class CBase
{
    public:
        void func1()        //不是虛函數(shù)
        {
            cout<<"CBase::func1()"<<endl;
            func2();        //在成員函數(shù)中調(diào)用虛函數(shù)
            func3();    
        }
        virtual void func2()
        {
            cout<<"CBase::func2()"<<endl;
        }
        void func3()
        {
            cout<<"CBase::func3()"<<endl;
        }
}; 

class CDerived:public CBase
{
    public:
        virtual void func2()
        {
            cout<<"CDerived::func2()"<<endl;
        }
        void func3()
        {
            cout<<"CDerived::func3()"<<endl;
        }
};

int main()
{
    CDerived d;
    d.func1();
    return 0;
}

CBase::func1()
CDerived::func2()
CBase::func3()

程序6-7 在構造函數(shù)與析構函數(shù)中調(diào)用虛函數(shù)

#include <iostream>
using namespace std;
class A
{
    public:
        virtual void hello()
        {
            cout<<"A::hello"<<endl;
        }
        virtual void bye()
        {
            cout<<"A::bye"<<endl;
        }
};

class B:public A
{
    public:
        virtual void hello()
        {
            cout<<"B::hello"<<endl;
        }
        B()
        {
            hello();
        }
        ~B()
        {
            bye();
        }
};

class C:public B
{
    public:
        virtual void hello()
        {
            cout<<"C::hello"<<endl;
        }
};

int main()
{
    C obj;
    return 0;
}

B::hello
A::bye

實現(xiàn)多態(tài)時畦粮,必須滿足的條件是:使用基類指針或引用來調(diào)用基類中聲明的虛函數(shù)。

程序6-8 多態(tài)與非多態(tài)的對比

#include <iostream>
using namespace std;

class A
{
    public:
        void func1()
        {
            cout<<"A::func1"<<endl;
        }
        virtual void func2()
        {
            cout<<"A::func2"<<endl;
        }
};

class B:public A
{
    public:
        virtual void func1()
        {
            cout<<"B::func1"<<endl;
        }
        void func2()
        {
            cout<<"B::func2"<<endl;
        }
};

class C:public B
{
    public:
        void func1()
        {
            cout<<"C::func1"<<endl;
        }
        void func2()
        {
            cout<<"C::func2"<<endl;
        }
};

int main()
{
    C obj;
    A *pa = &obj;
    B *pb = &obj;
    pa->func2();    //多態(tài) 
    pa->func1();    //不是多態(tài) 
    pb->func1();    //多態(tài) 
    return 0;
}

C::func2
A::func1
C::func1

虛析構函數(shù)

聲明虛析構函數(shù)的格式

virtual ~類名();

虛析構函數(shù)沒有返回值類型乖阵,沒有參數(shù)宣赔。

如果一個類的析構函數(shù)是虛函數(shù),則由它派生的所有子類的析構函數(shù)也是虛析構函數(shù)瞪浸。

程序6-9 不使用虛析構函數(shù)的情況

#include <iostream>
using namespace std;
class ABase
{
    public:
        ABase()
        {
            cout<<"ABase 構造函數(shù)"<<endl;
        }
        ~ABase()
        {
            cout<<"ABase 析構函數(shù)"<<endl;
        }
};

class Derived:public ABase
{
    public:
        int w,h;
        Derived()
        {
            cout<<"Derived 構造函數(shù)"<<endl;
            w = 4;
            h = 7;
        }
        ~Derived()
        {
            cout<<"Derived 析構函數(shù)"<<endl;
        }
};

int main()
{
    ABase *p = new Derived();
    delete p;
    return 0;
}

ABase 構造函數(shù)
Derived 構造函數(shù)
ABase 析構函數(shù)

改寫儒将,將基類析構函數(shù)修改為虛析構函數(shù)。

virtual ~ABase()
        {
            cout<<"ABase 析構函數(shù)"<<endl;
        }

ABase 構造函數(shù)
Derived 構造函數(shù)
Derived 析構函數(shù)
ABase 析構函數(shù)

純虛函數(shù)喝抽象類

純虛函數(shù)

純虛函數(shù)是聲明在基類中的虛函數(shù)对蒲,沒有具體的定義钩蚊,而由各派生類根據(jù)識記需要給出各自的定義贡翘。

聲明純虛函數(shù)的格式

virtual 函數(shù)類型 函數(shù)名(參數(shù)表)  = 0;

抽象類

包含純虛函數(shù)的類稱為抽象類。

純虛函數(shù)和函數(shù)體為空的虛函數(shù)的區(qū)別:

  • 純虛函數(shù)沒有函數(shù)體砰逻,而空的虛函數(shù)的函數(shù)體為空鸣驱。
  • 純虛函數(shù)所在的類是抽象類,不能直接進行實例化蝠咆;而空的虛函數(shù)所在的類是可以實例化的踊东。
    共同特點是:都可以派生出新的類,然后在新類中給出虛函數(shù)的實現(xiàn)刚操,而且這種新的實現(xiàn)具有多態(tài)特征闸翅。

程序6-10 抽象類示例

#include <iostream>
using namespace std;

class A
{
    private:
        int a;
    public:
        virtual void print() = 0;
        void func1()
        {
            cout<<"A_func1"<<endl;
        }
};

class B:public A
{
    public:
        void print();
        void func1()
        {
            cout<<"B_func1"<<endl;
        }
};

void B::print()
{
    cout<<"B_print"<<endl;
}

int main()
{
//  A a;        //錯誤,抽象類不能實例化
//  A *p = new A;   //錯誤菊霜,不能創(chuàng)建類A的實例
//  A b[2];     //錯誤坚冀,不能聲明抽象類的數(shù)組
    A *pa;      //正確,可以聲明抽象類的指針
    A *pb = new B;
    pb->print();
    B b;
    A *pc = &b;
    pc->func1();
    return 0;
}

B_print
A_func1

虛基類

定義虛基類的格式

class 派生類名 : virtual 派生方式 基類名
{
  派生類體
};

程序6-11 虛基類

#include <iostream>
using namespace std;
class A
{
    public:
        int a;
        void showa()
        {
            cout<<"a="<<a<<endl;
        }
};

class B:virtual public A
{
    public:
        int b;
};

class C:virtual public A
{
    public:
        int c;
};

class D:public B,public C
{
    //派生類D的兩個基類B鉴逞、C具有共同的基類A记某,
    //采用了虛繼承,從而使類D的對象中只包含著類A的1個實例
    public:
        int d; 
};

int main()
{
    D Dobj;
    Dobj.a = 11;
    Dobj.b = 22;
    Dobj.showa();
    cout<<"Dobj.b="<<Dobj.b<<endl;
}

a=11
Dobj.b=22
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末华蜒,一起剝皮案震驚了整個濱河市辙纬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叭喜,老刑警劉巖贺拣,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捂蕴,居然都是意外死亡譬涡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門啥辨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涡匀,“玉大人,你說我怎么就攤上這事溉知≡纱瘢” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵级乍,是天一觀的道長舌劳。 經(jīng)常有香客問我,道長玫荣,這世上最難降的妖魔是什么甚淡? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮捅厂,結果婚禮上贯卦,老公的妹妹穿的比我還像新娘资柔。我一直安慰自己,他們只是感情好撵割,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布贿堰。 她就那樣靜靜地躺著,像睡著了一般睁枕。 火紅的嫁衣襯著肌膚如雪官边。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天外遇,我揣著相機與錄音注簿,去河邊找鬼。 笑死跳仿,一個胖子當著我的面吹牛诡渴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菲语,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼妄辩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了山上?” 一聲冷哼從身側(cè)響起眼耀,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎佩憾,沒想到半個月后哮伟,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡妄帘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年楞黄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抡驼。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡鬼廓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出致盟,到底是詐尸還是另有隱情碎税,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布馏锡,位于F島的核電站雷蹂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏眷篇。R本人自食惡果不足惜萎河,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一荔泳、第九天 我趴在偏房一處隱蔽的房頂上張望蕉饼。 院中可真熱鬧虐杯,春花似錦、人聲如沸昧港。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽创肥。三九已至达舒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叹侄,已是汗流浹背巩搏。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趾代,地道東北人贯底。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像撒强,于是被迫代替她去往敵國和親禽捆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容