C++中的 虛函數 純虛函數 虛基類(virtual)

C++中的 虛函數 純虛函數 虛基類(virtual)
前言:需要了解三者的區別,必須要掌握多態的三個必要條件:

  1. 繼承
  2. 重載
  3. 父類指針指向子類對象。

虛函數 純虛函數 虛基類三者區別

1.虛函數是用於多態中virtual修飾父類函數,確保父類指針調用子類對象時,運行子類函數的。
2.純虛函數是用來定義接口的,也就是基類中定義一個純虛函數,基類不用實現,讓子類來實現。
3.虛基類是用來在多繼承中,比如菱形繼承中,如果兩個父類繼承自同一個類,就只實例化一個父類

①虛函數
第一個是沒有使用多態(只用繼承)的一般實現方式:

class A  
{  
public:  
    void printf(){  
        cout<<"printf A"<<endl;  
    }  
};  
class B : public A  
{  
public:  
    void printf(){  
        cout<<"printf B"<<endl;  
    }  
};  
int main(int argc, const char * argv[])  
{  
    A *a = new A();  
    a->printf();  
    B *b = new B();  
    b->printf();  
    return 0;  
}  

結果:

printf A
printf B

這是早期沒有多態的代碼,缺點:代碼冗餘
下面是使用了多態但是沒有引用virtual關鍵字的情況:

int main(int argc, const char * argv[])  
{  
    A *a = new B();  
    a->printf();  
    return 0;  
}  

結果:

printf A

因爲類的定義都一樣,所以就沒再寫出來了,當父類指針指向子類對象的時候,如果不使用virtual,父類調用方法的時候還是調用了父類自己的方法,沒有調用子類重寫的方法,所以就沒有實現到多態的作用,我們再來在父類中試試加入virtual關鍵字看看:

class A  
{  
public:  
virtual void printf(){  
        cout<<"printf A"<<endl;  
    }  
};  
class B : public A  
{  
public:  
//子類也可以不使用virtual,直接寫爲void printf()
    virtual void printf(){  
        cout<<"printf B"<<endl;  
    }  
};  
int main(int argc, const char * argv[])  
{  
    A *a = new B();  
    a->printf();  
    return 0;  
}  

結果

printf B

virtual是加入到父類中的,子類的代碼儘量加上virtual(方便被繼承的子類實現多態),也可以不加,main函數還是父類指針指向子類對象,結果終於可以打印到子類重寫的方法了,所以證實了虛函數是用於多態中virtual修飾父類該重寫的函數,確保父類指針調用子類對象時運行子類函數的。
② 純虛函數
純虛函數就是抽象接口,使用了純虛函數的類不能被實例化,定義了純虛函數的類不用寫純虛函數的實現,由子類實現,下面看代碼:

class A  
{  
public:  
    virtual void printf() =0;  
};  
void A::printf()//純虛函數可以不寫實現  
{  
    cout<<"printf A"<<endl;  
}  
class B : public A  
{  
public:  
    void printf(){  
        cout<<"printf B"<<endl;  
    }  
};  
int main(int argc, const char * argv[])  
{  
    A *a =new A();//編譯出錯,純虛函數的類不能實例化  
    a->printf();  
    return 0;  
}  

virtual void printf() = 0;這是虛函數的寫法,我在下面寫了虛函數的實現 void A::printf(),其實寫不寫都沒關係,寫了也起不了作用,然後我才main函數中嘗試吧純虛函數的類實例化,結果直接報錯,說明純虛函數是不能實例化的。

int main(int argc, const char * argv[])  
{  
    A *a =newB();//這裏使用了多態  
    a->printf();  
    return 0;  
}  

結果:

printf B

把main函數的a指向了子類的對象,結果可以正確打印出子類的方法。由此說明了純虛函數也是爲多態服務的,它的作用是定義一個接口,讓子類去實現。
③虛基類
虛基類是c++獨有的東西,因爲c++中有多繼承,也是關鍵字virtual相關的定義。

先來說說多繼承,如果爺爺類(暫把父類的父類暫定爲爺爺類 ),父類繼承自爺爺類。如果孫類繼承自多個父類(聽起來有點怪異),那麼如果不使用虛基類,就會實例化多個爺爺類對象(越說越離奇),編譯器會報錯,說有歧義性。如果父類繼承自虛基類,則可以解決多個父類不會實例化多個爺爺的問題,就是隻有一個爺爺。

class Grandfather{  
public:  
    int flag;  
    Grandfather(){  
        flag = 1;  
    }  
};  
class Father1:public Grandfather{  
public:  
    Father1(){  
        flag = 2;  
    }  
};  
class Father2:public Grandfather{  
public:  
    Father2(){  
        flag = 3;  
    }  
};  
class Son:public Father1,public Father2{  
};  
int main(int argc, const char * argv[])  
{  
    Son *son = new Son();  
    cout<<son->flag<<endl;//這裏編譯錯誤,flag訪問不明確,因爲兩個父類中都有flag變量,歧義  
    return 0;  
}  

如果沒有使用虛基類,多個父類繼承自同一個爺爺類,就會產生歧義,爲了不產生歧義,代碼可改爲(治標不治本):

cout<<son->Father1::flag<<endl;
cout<<son->Father2::flag<<endl;

如果父類繼承虛基類就不同了:

class Grandfather{  
public:  
    int flag;  
    Grandfather(){  
        flag = 1;  
        cout<<"Grandfather flag = "<<flag <<endl;  
    }  
};  
class Father1:virtual public Grandfather{  
public:  
    Father1(){  
        flag = 2;  
        cout<<"Father1 flag = "<<flag<<endl;  
    }  
};  
class Father2:virtual public Grandfather{  
public:  
    Father2(){  
        flag = 3;  
        cout<<"Father2 flag = "<<flag<<endl;  
    }  
};  
class Son:public Father1,public Father2{  
};  
int main(int argc, const char * argv[])  
{  
    Son *son = new Son();  
    cout<<son->flag<<endl;  
    return 0;  
}  

結果:

Grandfather flag = 1

Father1 flag = 2

Father2 flag = 3

3

現在,可以運行了,class Father2:virtual public Grandfather,就是繼承虛基類的寫法,爺爺對象只有一個,爺爺類的變量也只實例化了一次,那爲什麼最後打印出來的是3呢?看構造函數的順序就可以看出來了,現在構造了爺爺類,再構造第一個繼承的父類,最後繼承第二個繼承的父類,因此flag最後保持在第二個父類的修改值裏了。

C++中的 虛函數 純虛函數 虛基類(virtual)總的來說,虛函數 ,純虛函數是爲了多態服務,虛基類是爲了只實例化一次基類存在的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章