1. 程式人生 > >北京大學MOOC C++學習筆記(五)虛擬函式和多型

北京大學MOOC C++學習筆記(五)虛擬函式和多型

虛擬函式:

在類的定義中,前面有 virtual 關鍵字的成員函式就是虛擬函式。 class base { virtual int get() ; }; int base::get() { } virtual 關鍵字只用在類定義裡的函式宣告中,寫函式體時不用。

多型的表現形式一

  • 派生類的指標可以賦給基類指標。
  • 通過基類指標呼叫基類和派生類中的同名虛擬函式時:
  1. 若該指標指向一個基類的物件,那麼被呼叫是基類的虛擬函式;
  2. 若該指標指向一個派生類的物件,那麼被呼叫的是派生類的虛擬函式。

     這種機制就叫做“多型”。

class CBase {
    public:
        virtual void SomeVirtualFunction() { }
};
class CDerived:public CBase {
    public :
        virtual void SomeVirtualFunction() { }
};
int main() {
    CDerived ODerived;
    CBase * p = & ODerived;
    p -> SomeVirtualFunction(); //呼叫哪個虛擬函式取決於p指向哪種型別的物件
    return 0;
}

多型的表現形式二:

  • 派生類的物件可以賦給基類引用
  • 通過基類引用呼叫基類和派生類中的同名 虛擬函式時:
  1. 若該引用引用的是一個基類的物件,那麼被呼叫是基類的虛擬函式;
  2. 若該引用引用的是一個派生類的物件,那麼被呼叫的是派生類的虛擬函式。

這種機制也叫做“多型”。

class CBase {
    public:
    virtual void SomeVirtualFunction() { }
};
class CDerived:public CBase {
    public :
        virtual void SomeVirtualFunction() { }
};
int main() {
    CDerived ODerived;
    CBase & r = ODerived;
    r.SomeVirtualFunction(); //呼叫哪個虛擬函式取決於r引用哪種型別的物件
    return 0;
}

多型的作用:

在面向物件的程式設計中使用多型,能夠增強程式的可擴充性,即程式需要修改或增加功能的時候,需要改動和增加的程式碼較少。

多型使用例項:幾何形體處理程式

何形體處理程式: 輸入若干個幾何形體的引數, 要求按面積排序輸出。輸出時要指明形狀。 Input: 第一行是幾何形體數目n(不超過100).下面有n行,每行以一個字母c開頭. 若 c 是 ‘R’,則代表一個矩形,本行後面跟著兩個整數,分別是矩形的寬和高; 若 c 是 ‘C’,則代表一個圓,本行後面跟著一個整數代表其半徑 若 c 是 ‘T’,則代表一個三角形,本行後面跟著三個整數,代表三條邊的長度

Output: 按面積從小到大依次輸出每個幾何形體的種類及面積。每行一個幾何形體,輸 出格式為: 形體名稱:面積

Sample Input: 3 R 3 5 C 9 T 3 4 5 Sample Output Triangle:6 Rectangle:15 Circle:254.34

#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;
class CShape
{
    public:
        virtual double Area() = 0; //純虛擬函式
        virtual void PrintInfo() = 0;
};
class CRectangle:public CShape
{
    public:
        int w,h;
        virtual double Area();
        virtual void PrintInfo();
};
class CCircle:public CShape {
    public:
        int r;
        virtual double Area();
        virtual void PrintInfo();
};
class CTriangle:public CShape {
    public:
        int a,b,c;
        virtual double Area();
        virtual void PrintInfo();
};
double CRectangle::Area() {
    return w * h;
}
void CRectangle::PrintInfo() {
    cout << "Rectangle:" << Area() << endl;
}
double CCircle::Area() {
    return 3.14 * r * r ;
}
void CCircle::PrintInfo() {
    cout << "Circle:" << Area() << endl;
}
double CTriangle::Area() {
    double p = ( a + b + c) / 2.0;
    return sqrt(p * ( p - a)*(p- b)*(p - c));
}
void CTriangle::PrintInfo() {
    cout << "Triangle:" << Area() << endl;
}
CShape * pShapes[100];
int MyCompare(const void * s1, const void * s2);
int main()
{
    int i; int n;
    CRectangle * pr; CCircle * pc; CTriangle * pt;
    cin >> n;
    for( i = 0;i < n;i ++ ) {
        char c;
        cin >> c;
        switch(c) {
            case 'R':
                pr = new CRectangle();
                cin >> pr->w >> pr->h;
                pShapes[i] = pr;
                break;
            case 'C':
                pc = new CCircle();
                cin >> pc->r;
                pShapes[i] = pc;
                break;
            case 'T':
                pt = new CTriangle();
                cin >> pt->a >> pt->b >> pt->c;
                pShapes[i] = pt;
                break;
        }
    }
    qsort(pShapes,n,sizeof( CShape*),MyCompare);
    for( i = 0;i <n;i ++)
        pShapes[i]->PrintInfo();
    return 0;
}
int MyCompare(const void * s1, const void * s2)
{
    double a1,a2;
    CShape * * p1 ; // s1,s2 是 void * ,不可寫 “* s1”來取得s1指向的內容
    CShape * * p2;
    p1 = ( CShape * * ) s1; //s1,s2指向pShapes陣列中的元素,陣列元素的型別是CShape *
    p2 = ( CShape * * ) s2; // 故 p1,p2都是指向指標的指標,型別為 CShape **
    a1 = (*p1)->Area(); // * p1 的型別是 Cshape * ,是基類指標,故此句為多型
    a2 = (*p2)->Area();
    if( a1 < a2 )
        return -1;
    else if ( a2 < a1 )
        return 1;
    else
        return 0;
}

用基類指標陣列存放指向各種派生類物件的指標,然後遍歷該陣列,就能對各個派生類物件做各種操作,是很常用的做法 。

建構函式和解構函式中呼叫虛擬函式:

在建構函式和解構函式中呼叫虛擬函式,不是多型。編譯時即可確定,呼叫的函式是自己的類或基類中定義的函式,不會等到執行時才決定呼叫自己的還是派生類的函式。

虛解構函式:

  • 通過基類的指標刪除派生類物件時,通常情況下只調用基類的解構函式。但是,刪除一個派生類的物件時,應該先呼叫派生類的解構函式,然後呼叫基類的解構函式。
  • 解決辦法:把基類的解構函式宣告為virtual。派生類的解構函式可以virtual不進行宣告。通過基類的指標刪除派生類物件時,首先呼叫派生類的解構函式,然後呼叫基類的解構函式
  • 一般來說,一個類如果定義了虛擬函式,則應該將解構函式也定義成虛擬函式。或者,一個類打算作為基類使用,也應該將解構函式定義成虛擬函式。
  • 注意:不允許以虛擬函式作為建構函式
class son{
    public:
        ~son() {cout<<"bye from son"<<endl;};
};
class grandson:public son{
    public:
        ~grandson(){cout<<"bye from grandson"<<endl;};
};
int main(){
    son *pson;
    pson=new grandson();
    delete pson;
    return 0;
}
 輸出: bye from son 沒有執行grandson::~grandson()!!!
class son{
    public:
        virtual ~son() {cout<<"bye from son"<<endl;};
};
class grandson:public son{
    public:
        ~grandson(){cout<<"bye from grandson"<<endl;};
};
int main() {
    son *pson;
    pson= new grandson();
    delete pson;
    return 0;
}
: 輸出: bye from grandson
bye from son
執行grandson::~grandson() ,引起執行son::~son() !!!

虛擬函式的訪問許可權:

class Base {
    private:
        virtual void fun2() { cout << "Base::fun2()" << endl; }
};
class Derived:public Base {
    public:
        virtual void fun2() { cout << "Derived:fun2()" << endl; }
};
Derived d;
Base * pBase = & d;
pBase -> fun2(); // wrong
  •  編譯出錯是因為 fun2() 是Base的私有成員。即使執行到此時實際上呼叫的應該是Derived的公有成員 fun2()也不行,因為語法檢查是不考慮執行結果的。
  • 如果 將Base中的 private換成public,即使Derived中的fun2() 是private的,編譯依然能通過,也能正確呼叫Derived::fun2()。

純虛擬函式和抽象類:

純虛擬函式: 沒有函式體的虛擬函式

class A { private: int a; public: virtual void Print( ) = 0 ; // 純虛擬函式 void fun() { cout << "fun"; } };

  • 包含純虛擬函式的類叫抽象類
  • 抽象類只能作為基類來派生新類使用,不能建立抽象類的物件
  • 抽象類的指標和引用可以指向由抽象類派生出來的類的物件

A a ; // 錯,A 是抽象類,不能建立物件 A * pa ; // ok,可以定義抽象類的指標和引用 pa = new A ; //錯誤, A 是抽象類,不能建立物件

  • 在抽象類的成員函式內可以呼叫純虛擬函式,但是在建構函式或解構函式內部不能呼叫純虛擬函式。
  • 如果一個類從抽象類派生而來,那麼當且僅當它實現了基類中的所有純虛擬函式,它才能成為非抽象類。
class A {
    public:
        virtual void f() = 0; // 純虛擬函式
        void g( ) {  this->f( ) ; //ok}
        A( ){ //f( ); //  錯誤}
};
class B:public A{
    public:
    void f(){cout<<"B:f()"<<endl; }
};
int main(){
    B b;
    b.g();
    return 0;
}