1. 程式人生 > >C++中virtual關鍵字的用法

C++中virtual關鍵字的用法

關於virtual關鍵字的用法總結如下,有錯誤或者總結不到位的情況請能幫本人指出,非常感謝!

Virtual是C++ OO機制中很重要的一個關鍵字。只要是學過C++的人都知道在類Base中加了Virtual關鍵字的函式就是虛擬函式。

基類的函式呼叫如果有virtual則根據多型性呼叫派生類的,如果沒有virtual則是正常的靜態函式呼叫,還是呼叫基類的

1、虛擬函式的應用

看下面的一段程式碼的輸出結果:

class Base
{
public:Base(){}
public:
       virtual void print(){cout<<"Base";}
};
 
class Derived:public Base
{
public:Derived(){}
public:
       void print(){cout<<"Derived";}
};
 
int main()
{
       Base *point=new Derived();
       point->print();
}

Output:
Derived 
這也許會使人聯想到函式的過載,但稍加對比就會發現兩者是完全不同的:
(1)過載的幾個函式必須在同一個類中;
覆蓋的函式必須在有繼承關係的不同的類中
(2)覆蓋的幾個函式必須函式名、引數、返回值都相同;
過載的函式必須函式名相同,引數不同。引數不同的目的就是為了在函式呼叫的時候編譯器能夠通過引數來判斷程式是在呼叫的哪個函式。這也就很自然地解釋了為什麼函式不能通過返回值不同來過載,因為程式在呼叫函式時很有可能不關心返回值,編譯器就無法從程式碼中看出程式在呼叫的是哪個函數了。
(3)覆蓋的函式前必須加關鍵字Virtual;
過載和Virtual沒有任何瓜葛,加不加都不影響過載的運作。

再看下面林瑞博士講解的一段關於關鍵字Virtual的用法

#include <iostream.h>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }


};

class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
};

void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}

bp 和dp 指向同一地址,按理說執行結果應該是相同的,而事實上執行結果不同,所以他把原因歸結為C++的隱藏規則,其實這一觀點是錯的。決定bp和dp呼叫函式執行結果的不是他們指向的地址,而是他們的指標型別。“只有在通過基類指標或引用間接指向派生類子型別時多型性才會起作用”(C++ Primer 3rd Edition)。pb是基類指標,pd是派生類指標,pd的所有函式呼叫都只是呼叫自己的函式,和多型性無關,所以pd的所有函式呼叫的結果都輸出Derived::是完全正常的;pb的函式呼叫如果有virtual則根據多型性呼叫派生類的,如果沒有virtual則是正常的靜態函式呼叫,還是呼叫基類的,所以有virtual的f函式呼叫輸出Derived::,其它兩個沒有virtual則還是輸出Base::很正常啊,nothing surprise! 
所以並沒有所謂的隱藏規則,雖然《高質量C++/C 程式設計指南》是本很不錯的書,可大家不要迷信哦。記住“只有在通過基類指標或引用間接指向派生類子型別時多型性才會起作用”。

2、純虛擬函式
純虛擬函式定義如下:

C++語言為我們提供了一種語法結構,通過它可以指明,一個虛擬函式只是提供了一個可被子型別改寫的介面。但是,它本身並不能通過虛擬機制被呼叫。這就是純虛擬函式(purevirtual function)。 純虛擬函式的宣告如下所示:
class Query {
public:
// 宣告純虛擬函式
virtual ostream& print( ostream&=cout ) const = 0;
// ...
};
這裡函式聲明後面緊跟賦值0。

包含一個或多個純虛擬函式的類被編譯器識別為抽象基類。抽象基類不能被例項化,一般用於繼承。抽象基類只能作為子物件出現在後續的派生類中

3、虛擬繼承(virtual public)

在多繼承下,虛繼承就是為了解決菱形繼承中,B,C都繼承了A,D繼承了B,C,那麼D關於 A的引用只有一次,而不是 普通繼承的 對於A引用了兩次……

格式:可以採用public、protected、private三種不同的繼承關鍵字進行修飾,只要確保包含virtual就可以了。

class A
{
  void f1(){};
};
class B : public virtual  A{
  
 void f2(){};
};
虛繼承:在繼承定義中包含了virtual關鍵字的繼承關係;
虛基類:在虛繼承體系中的通過virtual繼承而來的基類,

#include 
using namespace std;
class Person{
   public:    Person(){ cout<<"Person構造"<<ENDL; }
           ~Person(){ cout<<"Person析構"<<ENDL; }
};
class Teacher : virtual public Person{
   public:    Teacher(){ cout<<"Teacher構造"<<ENDL; }
            ~Teacher(){ out<<"Teacher析構"<<ENDL; }
};
class Student : virtual public Person{
  public:      Student(){ cout<<"Student構造"<<ENDL; }
             ~Student(){ cout<<"Student析構"<<ENDL; }
};
class TS : public Teacher,  public Student{
public:            TS(){ cout<<"TS構造"<<ENDL; }
                 ~TS(){ cout<<"TS析構"<<ENDL; }
};
int main(int argc,char* argv[])
{
TS ts;
return 0;
}

這段程式碼的終端輸出結果為:
Person構造
Teacher構造
Student構造
TS構造
TS析構
Student析構
Teacher析構
Person析構
當Teacher類和Student類沒有虛繼承Person類的時候,也就是把virtual去掉時候終端輸出的結果為:
Person構造
Teacher構造
Person構造
Student構造
TS構造
TS析構
Student析構
Person析構
Teacher析構
Person析構

     大家可以很清楚的看到這個結果明顯不是我們所期望的。我們在構造TS的時候需要先構造他的基類,也就是Teacher類和Student類。而Teacher類和Student類由都繼承於Person類。這樣就導致了構造TS的時候例項化了兩個Person類。同樣的道理,析構的時候也是析構了兩次Person類,這是非常危險的,也就引發出了virtual的第三種用法,虛析構。

關於虛繼承的相關功能,本人也是一知半解,後續再做深入的研究