最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++中繼承與多態(tài)的基礎(chǔ)虛函數(shù)類詳解

 更新時間:2017年09月10日 11:13:34   作者:Dawn_sf  
這篇文章主要給大家介紹了關(guān)于C++中繼承與多態(tài)的基礎(chǔ)虛函數(shù)類的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。

前言

本文主要給大家介紹了關(guān)于C++中繼承與多態(tài)的基礎(chǔ)虛函數(shù)類的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧。

虛函數(shù)類

繼承中我們經(jīng)常提到虛擬繼承,現(xiàn)在我們來探究這種的虛函數(shù),虛函數(shù)類的成員函數(shù)前面加virtual關(guān)鍵字,則這個成員函數(shù)稱為虛函數(shù),不要小看這個虛函數(shù),他可以解決繼承中許多棘手的問題,而對于多態(tài)那他更重要了,沒有它就沒有多態(tài),所以這個知識點(diǎn)非常重要,以及后面介紹的虛函數(shù)表都極其重要,一定要認(rèn)真的理解~ 現(xiàn)在開始概念虛函數(shù)就又引出一個概念,那就是重寫(覆蓋),當(dāng)在子類的定義了一個與父類完全相同的虛函數(shù)時,則稱子類的這個函數(shù)重寫(也稱覆蓋)了父類的這個虛函數(shù)。這里先提一下虛函數(shù)表,后面會講到的,重寫就是將子類里面的虛函數(shù)表里的被重寫父類的函數(shù)地址全都改成子類函數(shù)的地址。

純虛函數(shù)

在成員函數(shù)的形參后面寫上=0,則成員函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口類)

抽象類不能實(shí)例化出對象。純虛函數(shù)在派生類中重新定義以后,派生類才能實(shí)例化出對象。

看一個例子:

class Person 
{ 
  virtual void Display () = 0; // 純虛函數(shù) 
protected : 
  string _name ;   // 姓名 
}; 
 
class Student : public Person 
{}; 

先總結(jié)一下概念:

1.派生類重寫基類的虛函數(shù)實(shí)現(xiàn)多態(tài),要求函數(shù)名、參數(shù)列表、返回值完全相同。(協(xié)變除外)

2.基類中定義了虛函數(shù),在派生類中該函數(shù)始終保持虛函數(shù)的特性。

3.只有類的成員函數(shù)才能定義為虛函數(shù)。

4.靜態(tài)成員函數(shù)不能定義為虛函數(shù)。

5.如果在類外定義虛函數(shù),只能在聲明函數(shù)時加virtual,類外定義函數(shù)時不能加virtual。

6.不要在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面調(diào)用虛函數(shù),在構(gòu)造函數(shù)和析構(gòu)函數(shù)中,對象是不完整的,可能會發(fā)生未定義的行為。

7.最好把基類的析構(gòu)函數(shù)聲明為虛函數(shù)。(why?另外析構(gòu)函數(shù)比較特殊,因?yàn)榕缮惖奈鰳?gòu)函數(shù)跟基類的析構(gòu)函數(shù)名稱不一樣,但是構(gòu)成覆蓋,這里是因?yàn)榫幾g器做了特殊處理)

8.構(gòu)造函數(shù)不能為虛函數(shù),雖然可以將operator=定義為虛函數(shù),但是最好不要將operator=定義為虛函數(shù),因?yàn)槿菀资褂脮r容易引起混淆.


上面概念大家可能都會問一句為什么要這樣? 這些內(nèi)容在接下來的知識里都能找到答案~ 好了那么我們今天的主角虛函數(shù)登場!!!!

何為虛函數(shù)表,我們寫一個程序,調(diào)一個監(jiān)視窗口就知道了。

下面是一個有虛函數(shù)的類:

#include<iostream> 
#include<windows.h> 
using namespacestd; 
 
class Base 
{ 
public: 
   virtual void func1() 
   {} 
 
   virtual void func2() 
   {} 
 
private: 
   inta; 
}; 
 
void Test1() 
{ 
   Base b1; 
} 
 
int main() 
 
{ 
   Test1(); 
   system("pause"); 
   return0; 
} 

我們現(xiàn)在點(diǎn)開b1的監(jiān)視窗口

這里面有一個_vfptr,而這個_vfptr指向的東西就是我們的主角,虛函數(shù)表。一會大家就知道了,無論是單繼承還是多繼承甚至于我們的菱形繼承虛函數(shù)表都會有不同的形態(tài),虛函數(shù)表是一個很有趣的東西。

我們來研究一下單繼承的內(nèi)存格局

仔細(xì)看下面代碼:

#include<iostream> 
#include<windows.h> 
using namespace std; 
 
 
class Base 
{ 
public: 
   virtual void func1() 
   { 
     cout<< "Base::func1"<< endl; 
   } 
 
   virtual void func2() 
   { 
     cout<< "Base::func2"<< endl; 
   } 
 
private: 
   inta; 
}; 
 
class Derive:public Base 
{ 
public: 
   virtual void func1() 
   { 
     cout<< "Derive::func1"<< endl; 
   } 
 
   virtual void func3() 
   { 
     cout<< "Derive::func3"<< endl; 
   } 
 
   virtual void func4() 
   { 
     cout<< "Derive::func4"<< endl; 
   } 
 
private: 
   int b; 
}; 

對于Derive類來說,我們覺得它的虛表里會有什么?

首先子類的fun1()重寫了父類的fun1() ,虛表里存的是子類的fun1() ,接下來父類的fun2() ,子類的fun3() , fun4()都是虛函數(shù),所以虛表里會有4個元素,分別為子類的fun1() ,父類fun2() ,子類fun3() ,子類fun4() 。然后我們調(diào)出監(jiān)視窗口看我們想的到底對不對呢?

 

我預(yù)計應(yīng)該是看到fun1() ,fun2() ,fun3() ,fun4()的虛函數(shù)表,但是呢這里監(jiān)視窗口只有兩個fun1() , fun2() ,難道我們錯了????

這里并不是這樣的,只有自己靠得住,我覺得這里的編譯器有問題,那我們就得自己探索一下了。 但是在探索之前我們必須來實(shí)現(xiàn)一個可以打印虛函數(shù)表的函數(shù)。

typedef void(*FUNC)(void); 
void PrintVTable(int* VTable) 
{ 
   cout<< " 虛表地址"<<VTable<< endl; 
 
   for(inti = 0;VTable[i] != 0; ++i) 
   { 
     printf(" 第%d個虛函數(shù)地址 :0X%x,->", i,VTable[i]); 
     FUNC f = (FUNC)VTable[i]; 
     f(); 
   } 
 
   cout<< endl; 
} 
 
 
int main() 
{ 
   Derive d1; 
   PrintVTable((int*)(*(int*)(&d1))); 
   system("pause"); 
   return0; 
} 

下圖來說一下他的緣由:


我們來使用這個函數(shù),該函數(shù)代碼如下:

//單繼承 
class Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base::func2" << endl; 
 } 
 
private: 
 int a; 
}; 
 
class Derive :public Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Derive::func1" << endl; 
 } 
 
 virtual void func3() 
 { 
  cout << "Derive::func3" << endl; 
 } 
 
 virtual void func4() 
 { 
  cout << "Derive::func4" << endl; 
 } 
 
private: 
 int b; 
}; 
typedef void(*FUNC)(void); 
void PrintVTable(int* VTable) 
{ 
   cout<< " 虛表地址"<<VTable<< endl; 
  
   for(inti = 0;VTable[i] != 0; ++i) 
   { 
     printf(" 第%d個虛函數(shù)地址 :0X%x,->", i,VTable[i]); 
     FUNC f = (FUNC)VTable[i]; 
     f(); 
   } 
  
   cout<< endl; 
} 
  
  
int main() 
{ 
   Derive d1; 
   PrintVTable((int*)(*(int*)(&d1))); //重點(diǎn) 
   system("pause"); 
   return0; 
} 

這里我就要講講這個傳參了,注意這里的傳參不好理解,應(yīng)當(dāng)細(xì)細(xì)的"品味".

PrintVTable((int*)(*(int*)(&d1)));

首先我們肯定要拿到d1的首地址,把它強(qiáng)轉(zhuǎn)成int*,讓他讀取到前4個字節(jié)的內(nèi)容(也就是指向虛表的地址),再然后對那個地址解引用,我們已經(jīng)拿到虛表的首地址的內(nèi)容(虛表里面存儲的第一個函數(shù)的地址)了,但是此時這個變量的類型解引用后是int,不能夠傳入函數(shù),所以我們再對他進(jìn)行一個int*的強(qiáng)制類型轉(zhuǎn)換,這樣我們就傳入?yún)?shù)了,開始函數(shù)執(zhí)行了,我們一切都是在可控的情況下使用強(qiáng)轉(zhuǎn),使用強(qiáng)轉(zhuǎn)你必須要特別清楚的知道內(nèi)存的分布結(jié)構(gòu)。

最后我們來看看輸出結(jié)果:



到底打印的對不對呢? 我們驗(yàn)證一下: 

這里我們通過&d1的首地址找到虛表的地址,然后訪問地址查看虛表的內(nèi)容,驗(yàn)證我們自己寫的這個函數(shù)是正確的。(這里VS還有一個bug,當(dāng)你第一次打印虛表時程序可能會崩潰,不要擔(dān)心你重新生成解決方案,再運(yùn)行一次就可以了。因?yàn)楫?dāng)你第一次打印是你虛表最后一個地方可能沒有放0,所以你就有可能停不下來然后崩潰。)我們可以看到d1的虛表并不是監(jiān)視器里面打印的那個樣子的,所以有時候VS也會有bug,不要太相信別人,還是自己靠得住。哈哈哈,臭美一下~

我們來研究一下多繼承的內(nèi)存格局

探究完了單繼承,我們來看看多繼承,我們還是通過代碼調(diào)試的方法來探究對象模型

看如下代碼:

class Base1 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base1::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base1::func2" << endl; 
 } 
 
private: 
 int b1; 
}; 
 
class Base2 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base2::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base2::func2" << endl; 
 } 
 
private: 
 int b2; 
}; 
 
 
class Derive : public Base1, public Base2 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Derive::func1" << endl; 
 } 
 
 virtual void func3() 
 { 
  cout << "Derive::func3" << endl; 
 } 
 
private: 
 int d1; 
}; 
 
typedef void(*FUNC) (); 
void PrintVTable(int* VTable) 
{ 
 cout << " 虛表地址>" << VTable << endl; 
 
 for (int i = 0; VTable[i] != 0; ++i) 
 { 
  printf(" 第%d個虛函數(shù)地址 :0X%x,->", i, VTable[i]); 
  FUNC f = (FUNC)VTable[i]; 
  f(); 
 } 
 cout << endl; 
} 
 
 
void Test1() 
{ 
 Derive d1; 
 //Base2虛函數(shù)表在對象Base1后面 
 int* VTable = (int*)(*(int*)&d1); 
 PrintVTable(VTable); 
 int* VTable2 = (int *)(*((int*)&d1 + sizeof (Base1) / 4)); 
 PrintVTable(VTable2); 
} 
int main() 
{ 
 Test1(); 
 system("pause"); 
 return 0; 
} 

現(xiàn)在我們現(xiàn)在知道會有兩個虛函數(shù)表,分別是Base1和Base2的虛函數(shù)表,但是呢!我們的子類里的fun3()函數(shù)怎么辦?它是放在Base1里還是Base2里還是自己開辟一個虛函數(shù)表呢?我們先調(diào)一下監(jiān)視窗口:


監(jiān)視窗口又不靠譜了。。。。完全沒有找到fun3().那我們直接看打印出來的虛函數(shù)表。


現(xiàn)在很清楚了,fun3()在Base1的虛函數(shù)表中,而Base1是先繼承的類,好了現(xiàn)在我們記住這個結(jié)論,當(dāng)涉及多繼承時,子類的虛函數(shù)會存在先繼承的那個類的虛函數(shù)表里。記住了!

我們現(xiàn)在來看多繼承的對象模型:


現(xiàn)在我們來結(jié)束一下上面我列的那么多概念現(xiàn)在我來逐一的解釋為什么要這樣.

1.為什么靜態(tài)成員函數(shù)不能定義為虛函數(shù)?

因?yàn)殪o態(tài)成員函數(shù)它是一個大家共享的一個資源,但是這個靜態(tài)成員函數(shù)沒有this指針,而且虛函數(shù)變只有對象才能能調(diào)到,但是靜態(tài)成員函數(shù)不需要對象就可以調(diào)用,所以這里是有沖突的.

2.為什么不要在構(gòu)造函數(shù)和析構(gòu)函數(shù)里面調(diào)用虛函數(shù)?

構(gòu)造函數(shù)當(dāng)中不適合用虛函數(shù)的原因是:在構(gòu)造對象的過程中,還沒有為“虛函數(shù)表”分配內(nèi)存。所以,這個調(diào)用也是違背先實(shí)例化后調(diào)用的準(zhǔn)則析構(gòu)函數(shù)當(dāng)中不適用虛函數(shù)的原因是:一般析構(gòu)函數(shù)先析構(gòu)子類的,當(dāng)你在父類中調(diào)用一個重寫的fun()函數(shù),虛函數(shù)表里面就是子類的fun()函數(shù),這時候已經(jīng)子類已經(jīng)析構(gòu)了,當(dāng)你調(diào)用的時候就會調(diào)用不到.

現(xiàn)在我在寫最后一個知識點(diǎn),為什么盡量最好把基類的析構(gòu)函數(shù)聲明為虛函數(shù)??

現(xiàn)在我們再來寫一個例子,我們都知道平時正常的實(shí)例化對象然后再釋放是沒有一點(diǎn)問題的,但是現(xiàn)在我這里舉一個特例:

我們都知道父類的指針可以指向子類,現(xiàn)在呢我們我們用一個父類的指針new一個子類的對象。

//多態(tài) 析構(gòu)函數(shù) 
class Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base::func2" << endl; 
 } 
 
 virtual ~Base() 
 { 
  cout << "~Base" << endl; 
 } 
 
private: 
 int a; 
}; 
 
class Derive :public Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Derive::func1" << endl; 
 } 
 virtual ~Derive() 
 { 
  cout << "~Derive"<< endl; 
 } 
private: 
 int b; 
}; 
 
void Test1() 
{ 
 Base* q = new Derive; 
 delete q; 
} 
int main() 
{ 
 Test1(); 
 system("pause"); 
 return 0; 
} 

這里面可能會有下一篇要說的多態(tài),所以可能理解起來會費(fèi)勁一點(diǎn)。

注意這里我先讓父類的析構(gòu)函數(shù)不為虛函數(shù)(去掉virtual),我們看看輸出結(jié)果:


這里它沒有調(diào)用子類的析構(gòu)函數(shù),因?yàn)樗且粋€父類類型指針,所以它只能調(diào)用父類的析構(gòu)函數(shù),無權(quán)訪問子類的析構(gòu)函數(shù),這種調(diào)用方法會導(dǎo)致內(nèi)存泄漏,所以這里就是有缺陷的,但是C++是不會允許自己有缺陷,他就會想辦法解決這個問題,這里就運(yùn)用到了我們下次要講的多態(tài)?,F(xiàn)在我們讓加上為父類析構(gòu)函數(shù)加上virtual,讓它變回虛函數(shù),我們再運(yùn)行一次程序的:


誒! 子類的虛函數(shù)又被調(diào)用了,這里發(fā)生了什么呢??  來我們老方法打開監(jiān)視窗口。


剛剛這種情況就是多態(tài),多態(tài)性可以簡單地概括為“一個接口,多種方法”,程序在運(yùn)行時才決定調(diào)用的函數(shù),它是面向?qū)ο缶幊填I(lǐng)域的核心概念。這個我們下一個博客專門會總結(jié)多態(tài).

當(dāng)然虛函數(shù)的知識點(diǎn)遠(yuǎn)遠(yuǎn)沒有這么一點(diǎn),這里可能只是冰山一角,比如說菱形繼承的虛函數(shù)表是什么樣?然后菱形虛擬繼承又是什么樣子呢? 這些等我總結(jié)一下會專門寫一個博客來討論菱形繼承。虛函數(shù)表我們應(yīng)該已經(jīng)知道是什么東西了,也知道單繼承和多繼承中它的應(yīng)用,這些應(yīng)該就足夠了,這些其實(shí)都是都是為你讓你更好的理解繼承和多態(tài),當(dāng)然你一定到分清楚重寫,重定義,重載的他們分別的含義是什么. 這一塊可能有點(diǎn)繞,但是我們必須要掌握.

總結(jié)

以上就是對虛函數(shù)的一點(diǎn)簡單見解,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • Qt6.0開發(fā)環(huán)境搭建步驟(圖文)

    Qt6.0開發(fā)環(huán)境搭建步驟(圖文)

    這篇文章主要介紹了Qt6.0開發(fā)環(huán)境搭建步驟(圖文),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • c語言實(shí)現(xiàn)php的trim標(biāo)簽

    c語言實(shí)現(xiàn)php的trim標(biāo)簽

    本文給大家介紹的是使用C語言實(shí)現(xiàn)php的trim標(biāo)簽功能的代碼,非常的實(shí)用,其主要作用是清除字符串開頭結(jié)尾除空白,有需要的小伙伴可以參考下。
    2016-01-01
  • C?語言輸入輸出庫函數(shù)講解(最新推薦)

    C?語言輸入輸出庫函數(shù)講解(最新推薦)

    輸入輸出函數(shù)能夠讓程序和用戶或者文件進(jìn)行交互,這篇文章主要介紹了C?語言輸入輸出庫函數(shù)講解,需要的朋友可以參考下
    2025-04-04
  • QT利用QPdfWriter實(shí)現(xiàn)繪制PDF(支持表單輸出)

    QT利用QPdfWriter實(shí)現(xiàn)繪制PDF(支持表單輸出)

    這篇文章主要為大家詳細(xì)介紹了QT如何利用QPdfWriter實(shí)現(xiàn)繪制PDF,并可以支持表單輸出。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-01-01
  • C++中多才多藝的 const

    C++中多才多藝的 const

    在C++中,關(guān)鍵字const可以用來修飾任何作用域內(nèi)的變量、函數(shù)參數(shù)、函數(shù)本體、函數(shù)返回值、成員函數(shù)、迭代器,也可以用來修飾指針本身和指針目標(biāo),可謂多才多藝,我們要詳細(xì)了解其內(nèi)部細(xì)節(jié),以及邏輯奧秘,讓這把多功能瑞士軍刀盡情發(fā)揮其作用,需要的朋友可以參考一下
    2021-09-09
  • C++虛函數(shù)注意事項(xiàng)

    C++虛函數(shù)注意事項(xiàng)

    這篇文章主要給大家分享了EC++虛函數(shù)注意事項(xiàng),
    2022-01-01
  • C語言深入講解指針與結(jié)構(gòu)體的使用

    C語言深入講解指針與結(jié)構(gòu)體的使用

    指針提供了對地址操作的一種方法,因此,使用指針可使得C語言能夠更高效地實(shí)現(xiàn)對計算機(jī)底層硬件的操作。另外,通過指針可以更便捷地操作數(shù)組。C數(shù)組允許定義可存儲相同類型數(shù)據(jù)項(xiàng)的變量,結(jié)構(gòu)是C編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許您存儲不同類型的數(shù)據(jù)項(xiàng)
    2022-05-05
  • C++?用紅黑樹模擬實(shí)現(xiàn)set、map的示例代碼

    C++?用紅黑樹模擬實(shí)現(xiàn)set、map的示例代碼

    set、map的底層結(jié)構(gòu)是紅黑樹,它們的函數(shù)通過調(diào)用紅黑樹的接口來實(shí)現(xiàn),本文主要介紹了C++?用紅黑樹模擬實(shí)現(xiàn)set、map,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • C++中實(shí)現(xiàn)接口繼承與實(shí)現(xiàn)繼承的方法及它們的區(qū)別

    C++中實(shí)現(xiàn)接口繼承與實(shí)現(xiàn)繼承的方法及它們的區(qū)別

    在 C++ 中,接口繼承和實(shí)現(xiàn)繼承是兩種不同的繼承方式,它們在設(shè)計模式、代碼復(fù)用和多態(tài)性方面有著不同的應(yīng)用,下面將分別解釋這兩者的概念、實(shí)現(xiàn)方式及其區(qū)別,感興趣的朋友一起看看吧
    2024-12-12
  • c語言經(jīng)典習(xí)題之逆序字符串詳解

    c語言經(jīng)典習(xí)題之逆序字符串詳解

    這篇文章主要為大家介紹了c語言習(xí)題之逆序字符串,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01

最新評論

鸡泽县| 崇礼县| 玉龙| 五莲县| 赣州市| 汤原县| 兖州市| 高唐县| 怀柔区| 青铜峡市| 碌曲县| 长岛县| 牡丹江市| 湖北省| 大姚县| 崇左市| 河北省| 东阿县| 崇礼县| 海盐县| 江门市| 内黄县| 防城港市| 图木舒克市| 浪卡子县| 儋州市| 剑阁县| 曲麻莱县| 青龙| 龙州县| 墨玉县| 永胜县| 天津市| 海丰县| 临沂市| 岳西县| 东乌珠穆沁旗| 马鞍山市| 温泉县| 中山市| 依安县|