1. 程式人生 > 其它 >C++ virtual關鍵字深入理解

C++ virtual關鍵字深入理解

技術標籤:C++

對virtual關鍵的理解需要結合其應用場景來分析。以下從多個角度來深入理解virtual關鍵字。

1.virtual關鍵字主要是什麼作用?

c++中的函式呼叫預設不適用動態繫結。要觸發動態繫結,必須滿足兩個條件:第一,指定為虛擬函式;第二,通過基類型別的引用或指標呼叫(多型產生的條件)。
由此可見,virtual主要主要是實現動態繫結。

2.哪些情況下可以使用virtual關鍵字?

virtual可用來定義類函式和應用到虛繼承(一個是多型應用到函式,一個虛繼承應用到類)。

友元函式 建構函式 static靜態函式 不能用virtual關鍵字修飾(請思考為什麼?—靜態鏈編);

普通成員函式 和解構函式 可以用virtual關鍵字修飾;

3.virtual函式的效果

class GrandFather
{ public:
 GrandFather() {}
 virtual void fun()
 {  cout << "GrandFather call function!" << endl;
  }
};


class Father : public GrandFather
{ public:
  Father() {}
  void fun()
  {  cout << "Father call function!" << endl;
  }
};

class Son : public Father
{  public:
   Son() {}
   void fun()
  {    cout << "Son call function!" << endl;
  }
};

void print(GrandFather* father)
{ father->fun();
}

int _t main(int argc, _TCHAR* argv[])
{ Father * pfather = new Son;
        pfather->fun();
        GrandFather * pgfather = new Father;
        print(pgfather);
 return 0;
}

輸出為 Son call function

   Father call function

這就是實現多型的效果!

4.virtual的繼承性

只要基函式定義了virtual,繼承類的該函式也就具有virtual屬性
即 GrandFather Father Son同時定義virtual void fun()與GrandFather一個定義virtual void fun效果是一樣的

5.虛解構函式

class GrandFather
{ public:
  GrandFather() {}
  virtual void fun()
 {  cout << "GrandFather call function!" << endl;
 }


 ~GrandFather()
 { cout << "GrandFather destruction!" << endl;
 }
};

class Father : public GrandFather
{ public:
  Father() {}
  void fun()
  {   cout << "Father call function!" << endl;
  }

 ~Father()
 { cout << "Father destruction!" << endl;
 }
};

class Son : public Father
{ public:
  Son() {}
  void fun()
 { cout << "Son call function!" << endl;
 }

  ~Son()
 { cout << "Son destruction!" << endl;
  }
};

void print(GrandFather* p)
{ p->fun();
}

int _tmain(int argc, _TCHAR* argv[])
{ Father * pfather = new Son;
  delete pfather;
  return 0;
}

以上程式碼輸出:
Father destruction! GrandFather destruction!
執行了Son的建構函式,沒執行Son的解構函式,故把GrandFather的解構函式設定為virtual
則輸出:
Son destruction! Father Destruction! GrandFather destruction!

6. 純虛擬函式

純虛擬函式定義如下:

class GrandFather
{ public:
  GrandFather() {}
  virtual void fun() = 0
 { cout << "GrandFather call function!" << endl;
 }


 virtual ~GrandFather()
 {
  cout << "GrandFather destruction!" << endl;
 }
};

純虛擬函式為後代類提供可覆蓋的介面,但這個類中的版本決不會呼叫。
含有(或繼續)一個或多個純虛擬函式的類是抽象基類,抽象基類不能例項化!
繼承類只有重寫這個接口才能被例項化

7.虛繼承

虛繼承主要解決交叉繼承帶來的問題。這裡給出一片參考文章c++虛繼承。
給一個例子如下

class GrandFather
{ public:
  GrandFather() {}
  void fun()
 { cout << "GrandFather call function!" << endl;
 }


 virtual ~GrandFather()
 { cout << "GrandFather destruction!" << endl;
 }
};

class Father1 : public GrandFather
{ public:
  Father1() {}
  void fun()
 { cout << "Father call function!" << endl;
 }
};

class Father2 : public GrandFather
{ public:
  Father2() {}
  void fun()
 { cout << "Father call function!" << endl;
 }

};

class Son : public Father1, public Father2
{ public:
  Son() {}
 //void fun()
 //{
 // cout << "Son call function!" << endl;
 //}
};

void print(GrandFather* p)
{ p->fun();
}

int _tmain(int argc, _TCHAR* argv[])
{ Son* son = new Son;
  son->fun();
  return 0;
}

編譯時會提示報錯對fun的訪問不明確
如果Father1和Father2都用虛繼承繼承GrandFather類則可以解決這個問題

8. 建構函式和解構函式中的虛擬函式

如果在建構函式或解構函式中呼叫虛擬函式,則執行的是為建構函式或解構函式自身型別定義的版本

9.虛擬函式的實現機制

關於虛擬函式的實現機制,我們以後在介紹。

10.小結

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

11.例子

class classA
{ public:
  classA()
 { clear();
 }
 virtual ~classA()
 {
  }
 void clear()
 {  memset(this , 0 , sizeof(*this));
 }
 virtual void func()
 {  printf("func\n");
 }
};


class classB : public classA
{
};

int main(void)
{
 classA oa;
 classB ob;
 classA * pa0 = &oa;
 classA * pa1 = &ob;
 classB * pb = &ob;
 oa.func(); // 1
 ob.func(); // 2
 pa0->func(); // 3
 pa1->func(); // 4
 pb->func(); // 5
 return 0;
}

補充一個例子,這個程式輸出依次是

func
func
出錯
func
func

談談我的理解,當

classA oa;
oa.func();

不存在動態呼叫的過程,所以func雖然是虛擬函式,但是函式呼叫不通過虛表訪問,所以即使

memset(this , 0 , sizeof(*this));

找不到虛表地址也沒有關係
在執行classB ob;的時候,注意memset的是classA的地址,所有ob的虛表是存在的
即是如下,通過指標或引用(動態繫結)訪問oa的func函式(需要從虛表訪問),會出錯
訪問ob的func和函式,無論靜態訪問還是動態訪問,都不會出錯

當把classB的程式碼改成如下時

class classB : public classA
classB()
 {  clear();
 }
 virtual ~classB()
 {
 }
 void clear()
 {  memset(this , 0 , sizeof(*this));
 }
 輸出為

func
func
出錯
出錯
出錯