基礎備忘:多態的實現(重載、虛函數、抽象類別)

來源:互聯網
上載者:User

1.函數重載

由靜態聯編支援的多態性稱為編譯時間的多態性或靜態多態性,也就是說,確定同名操作的具體操作對象的過程是在編譯過程中完成的。在C++中,可以用函數重載和運算子多載來實現編譯時間的多態性。

2.虛函數

由動態聯編支援的多態性稱為運行時的多太性或動態多態性,也就是說,確定同名操作的具體操作對象的過程是在運行過程中完成的。在C++中,可以用虛函數來實現運行時的多態性。

2.1.虛函數定義

虛函數的定義是在基類中進行的,即把基類中需要定義為虛函數的成員函式宣告為virtual。當基類中的某個成員函數被聲明為虛函數後,就可以在衍生類別中被重新定義。在衍生類別中重定義時,其函數原型,包括傳回型別、函數函數名、參數個數和類型、參數的順序都必須與基類中的原型完全一致。

定義形式為:

virtual <函數類型><函數名>(參數表){函數體}

例如:解決博文多態概念時的問題代碼如下:

#include<iostream>using namespace std;class Animal{      public:             void sleep()             {                  cout<<"Animal Sleep"<<endl;                  }            virtual void breathe()//加上virtual              {                  cout<<"Animal Breathe"<<endl;                  }      };class Fish:public Animal{      public:             void breathe()             {                  cout<<"Fish Bubble"<<endl;                  }      };int main(){    Fish fs;    Animal *an=&fs;    an->breathe();    system("pause");    }

只是加了一個virtual問題就解決了。輸出結果為:

事實上,當將基類中的成員函數breathe()聲明為virtual即虛函數時,編譯器在編譯的時候發現Animal類中有虛函數,此時編譯器會為每個包含虛函數的類建立一個虛表,該表是一個一維數組,在這個數組中存放每個虛函數的地址。上述代碼中,Animal類和Fish類都包含了一個虛函數breathe(),因此編譯器會為這兩個類分別建立一個虛表,如所示。

類Animal和類Fish的虛表

在上述代碼中,當Fish類的fs物件建構完畢後,其內部的虛表指標也就被初始化為指向Fish類的虛表。在轉換後,調用an->breathe(),由於an實際指向的是Fish類的對象,該對象內部的虛表指標指向的是Fish類的虛表,因此最終調用的是Fish類的breathe()函數。

使用派生對象指標時應注意的問題:

1.聲明為指向基類對象的指標可以指向它的公有衍生類別的對象,但不允許指向它的私人衍生類別的對象。

2.允許聲明為指向基類對象的指標指向它的公有衍生類別的對象,但不允許將一個聲明為指向衍生類別對象的指標指向基類的對象。

3.聲明為指向基類對象的指標,當其指向它的公有衍生類別的對象時(滿足第1條),只能直接存取衍生類別中從基類繼承下來的成員,不能直接存取公有衍生類別中定義的成員。要想訪問其公有衍生類別中的成員,可將基類指標用顯式類型轉換方式轉換為衍生類別指標。例如:

#include<iostream>using namespace std;class B{      public:             void vf()             {                  cout<<"This is class B"<<endl;                  }      };class D:public B{      public:             void vf()             {                  cout<<"This is class D"<<endl;                  }      };int main(){    B b,*pb;    D d,*pd;    pb= &b;    pb->vf();    pb=&d;//滿足第1第2條。     pb->vf(); // pd=&b;//違背第2條。invalid conversion from `B*' to `D*'  // pd->vf();    pd=(D*)&b;//滿足第3條。     pd->vf();    pd=&d;    pd->vf();    system("pause");    }

如果把vf改成virtual類型的,有:

#include<iostream>using namespace std;class B{      public:             virtual void vf()             {                  cout<<"This is class B"<<endl;                  }      };class D:public B{      public:             void vf()             {                  cout<<"This is class D"<<endl;                  }      };int main(){    B b,*pb;    D d,*pd;    pb= &b;    pb->vf();    pb=&d;//滿足第1第2條。     pb->vf();  //pd=&b;//違背第2條。invalid conversion from `B*' to `D*'   //pd->vf();    pd=(D*)&b;//滿足第3條。 這裡是多態。    pd->vf();    pd=&d;    pd->vf();    system("pause");    }

虛函數可以很好的實現多態,在使用虛函數時應注意如下問題

1.虛函數的聲明只能出現在類函數原型的聲明中,不出現在函數體實現的時候,而且,基類中只有保護成員或公有成員才能被聲明為虛函數。

2.在衍生類別中重新定義虛函數時,關鍵字virtual可以寫也可以不寫,但是在容易引起混亂時,應該寫上該關鍵字。

3.動態聯編只能通過成員函數來調用或者通過指標、引用來訪問虛函數,如果以對象名的形式來訪問虛函數,將採用靜態聯編。

衍生類別中重新定義基類中的虛函數,是函數重載的另一種形式,但它與函數重載 又有如下區別:

1.一般的函數重載,要求其函數的參數或參數類型必須有所不同,函數的傳回型別也可以不同。

2.重載一個虛函數時,要求函數名、傳回型別、參數個數、參數類型和參數順序都必須與基類中的虛函數完全一致。

3.如果僅傳回型別不同,其餘相同,則系統會給出錯誤資訊。

4.如果函數名相同,而參數個數、參數的類型或參數的順序不同,系統認為是普通的函數重載,虛函數的特性將被丟掉。

2.2 多級繼承和虛函數

原生態多級繼承例子:

#include<iostream>using namespace std;class B{      public:             int ms;      public:            void set()             {                  cout<<"Base set"<<endl;                  }      };class D:public B{      public:             void set()             {                  cout<<"D set"<<endl;                  }      };class DD:public D{      };class DDD:public DD{      public:             void set()             {                  cout<<"DDD set"<<endl;                  }      };class DDDD:public DDD{            };class DDDDD:public DDDD{      };int main(){    DD d;    d.set();    DDDD dddd;    dddd.set();    DDDDD ddddd;    ddddd.set();    system("pause");    }

上述代碼結果說明,如果類中沒有對應的成員函數,則會向上一級一級找,直到出現所需要的成員函數。如上面的set。d.set在DD類中無set類,就向上找,找到離自己最近的父類的成員函數。

多級繼承可以看做是多個單繼承的組合,多級繼承的虛函數與單繼承的虛函數的調用相同,一個虛函數無論被繼承多少次,仍保持其虛函數的特性,與繼承的次數無關。例如:

#include<iostream>using namespace std;class Base{      public:             virtual ~Base(){}             virtual void func()             {                     cout<<"Base func"<<endl;                     }      };class Derived1:public Base{      void func()      {           cout<<"Derived1 func"<<endl;           }      };class Derived2:public Derived1{      void func()      {           cout<<"Derived2 func"<<endl;           }      };void test(Base &b)//引用方式 {     b.func();     }int main(){    Base b;    Derived1 d1;    Derived2 d2;    test(b);    test(d1);    test(d2);    system("pause");    }

上述代碼中在解構函式前面加上關鍵字virtual,則該解構函式就稱為虛解構函式。

上述代碼擴充例子:

#include<iostream>using namespace std;class Base{      public:             virtual ~Base(){}             virtual void func()             {                     cout<<"Base func"<<endl;                     }      };class Derived1:public Base{      void func()      {           cout<<"Derived1 func"<<endl;           }      };class Derived2:public Derived1{  /*    void func()      {           cout<<"Derived2 func"<<endl;           }*/      };class Derived3:public Derived2{      void func()      {           cout<<"Derived3 func"<<endl;           }      };class Derived4:public Derived3{      void func()      {           cout<<"Derived4 func"<<endl;           }      };void test(Base &b)//引用方式 {     b.func();     }int main(){    Base b;    Derived1 d1;    Derived2 d2;    Derived3 d3;    Derived4 d4;    test(b);    test(d1);    test(d2);    test(d3);    test(d4);    system("pause");    }


注意d2使用的是其父類的成員函數。

在使用虛解構函式時,要注意以下兩點:

1.只要基類的解構函式被聲明為虛函數,則衍生類別的解構函式,無論是否使用virtual關鍵字進行聲明,都自動成為虛函數。

2.如果基類的解構函式為虛函數,則當衍生類別未定義解構函式時,編譯器所產生的解構函式也為虛函數。

一般來說,在程式中最好把基類的解構函式聲明為虛函數。這將使所有衍生類別的解構函式自動成為虛函數。這樣,如果程式中用delete運算子準備刪除一個對象,而delete運算子操作對象是指向衍生類別的基類指標,系統會調用相應的解構函式;否則系統只執行蕨類的解構函式,而不執行衍生類別的解構函式,從而可能導致異常情況的發生。

建構函式不能聲明為虛函數。這是因為在執行建構函式時類對象還未完成建立過程,當然談不上函數與類對象的關聯。

補充例子:

#include <iostream> using namespace std; class A { protected:            int m_data; public:         A(int data = 0):m_data(data) { }         int GetData() { return doGetData(); }         virtual int doGetData() { return m_data; } }; class B:public A {       protected:                  int m_data;       public:               B(int data = 1): m_data(data) { }               int doGetData() { return m_data; } }; class C:public B {       protected: int m_data;       public:               C(int data = 2):m_data(data) { } }; int main() {     C c(10);     cout << c.GetData() << endl;     cout << c.A::GetData() << endl;     cout << c.B::GetData() << endl;     cout << c.C::GetData() << endl;     cout << c.doGetData() << endl;     cout << c.A::doGetData() << endl;     cout << c.B::doGetData() << endl;     cout << c.C::doGetData() << endl;     system("pause");    return 0;     } 

1    //C中getdata未定義,B中也是,所以調用A的。A的dogetdata是虛函數,所以調用B的,返回B::m_data1    //同上1    //同上1    //同上1    //C中dogetdata未定義,調用B的,返回B::m_data0    //直接調用A中的dogetdata,返回A::m_data1    //調用B中doggetdata,返回B::m_data1    //同c.doGetData()Press any key to continue . . .

3.純虛函數與抽象類別

抽象類別是一種特殊的類,它提供統一的操作介面。建立抽象類別是為了多態地使用抽象類別成員函數。抽象類別是包含純虛函數的類。

3.1 純虛函數

定義形式:

    virtual <函數類型><函數名>(參數表)=0;

純虛函數的作用是為衍生類別提供一個統一的介面,純虛函數的實現可以留給衍生類別來完成。一般來說,一個抽象類別中帶有至少一個純虛函數。

例:

#include<iostream>using namespace std;class Point{      protected:                int x0,y0;      public:             Point(int i,int j)             {                       x0=i;                       y0=j;                       }      virtual void set()=0;      virtual void draw()=0;      };class Line:public Point{      protected:                int x1,y1;      public:             Line(int i=0,int j=0,int m=0,int n=0):Point(i,j)             {                      x1=m;                      y1=n;                      }             void set(){cout<<"Line_set()"<<endl;}             void draw(){cout<<"Line_draw()"<<endl;}      };class Ellipse:public Point{      protected:                int x2,y2;      public:             Ellipse(int i=0,int j=0,int p=0,int q=0):Point(i,j)             {                         x2=p;                         y2=q;                      }             void set(){cout<<"Ellipse_set()"<<endl;}             void draw(){cout<<"Ellipse_draw()"<<endl;}      };void drawobj(Point *p){     p->draw();     }void setobj(Point *p){     p->set();     }int main(){  Line *li=new Line();//new Line;  drawobj(li);  setobj(li);  cout<<endl;     Ellipse *el=new Ellipse();//new Ellipse;   drawobj(el);   setobj(el);   cout<<endl;       Line *li2=new Line;   drawobj(li2);   setobj(li2);   cout<<endl;      Ellipse elp;   drawobj(&elp);   setobj(&elp);   cout<<endl;           system("pause");    } 

在上述代碼中,類Line和類Ellipse都公有繼承自類Point,在該程式中要實現多態,就必須定義set()函數和draw()函數為虛函數。同時,由於點是不需要在應用程式中畫出的,因此Point的set()函數和draw()函數定義為虛函數。在主函數main()中,建立Line對象和Ellipse對象後,調用這兩個同名的函數即可。這就是虛函數的作用,它一般用於基類函數沒有具體操作,而衍生類別中該函數可能需要的場合。

3.2 抽象類別

包含虛函數的類稱為抽象類別。抽象類別是一種特殊的類,是為了抽象和設計的目的而建立的,處於繼承階層的較上層。抽象類別是不能建立對象的,在實際中為了強調一個類是抽象類別,可將該類的建構函式聲明為保護的存取控制許可權。

抽象類別的主要作用是將有關的類組織在一個繼承層次中,由抽象類別來為它們提供一個公用的根,相關的子類是從這個根派生出來的。抽象類別刻畫了一組子類的操作介面的通用語義,這些語義也傳給子類。一般而言,抽象類別只描述這組子類共同的操作介面,而完整的實現留給子類。使用抽象類別時應該注意以下問題:

1.抽象類別只能用做其他類的基類,不能建立抽象類別對象,因為它的純虛函數沒有定義功能,其純屬虛函數的實現由衍生類別給出。

2.抽象類別不能用做參數類型、函數的傳回型別或顯式轉換的類型。

3.可以聲明抽象類別的對象指標或對象引用,從而可以訪問衍生類別對象成員,實現動態聯編。

4.若衍生類別中沒有給出抽象類別的所有純虛函數的函數體,衍生類別仍是一個抽象類別。若抽象類別的衍生類別中給出了所有純虛函數的函數體,這個衍生類別不再是一個抽象類別,它可以建立自己的對象了。例:

#include<iostream>using namespace std;class Vehicle{      protected:                float speed;                int total;      public:             Vehicle(float speed,int total)             {                           Vehicle::speed = speed;//成員變數和函數參數重名,注意加上::以區分成員變數和函數參數,否則初始化會有問題                           Vehicle::total = total;//或者this->total = total;                           }             virtual void ShowMember()=0;      };class Car:public Vehicle{      protected:                int aird;      public:             Car(int aird,float speed,int total):Vehicle(speed,total)             {                     Car::aird = aird;                     }             void ShowMember()             {                  cout<<"Speed is: "<<speed<<endl;                  cout<<"Toatal is: "<<total<<endl;                  cout<<"Aird is: "<<aird<<endl;                  }      };int main(){    Car c(250,150,4);    c.ShowMember();    system("pause");    }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.