程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 動態聯編和靜態聯編

動態聯編和靜態聯編

編輯:C++入門知識

關於 動態聯編 和 靜態聯編 這個概念,自己聽了老師上課講的課仍然沒有明白原理。   那麼既然這樣,只能自己去學習了。     首先我們知道的是,動態聯編 和 靜態聯編 都是多態性的一種體現。   關於面向對象的三個基本要素:封裝(類型抽象), 繼承 和 多態。   首先我們從概念性上面了解了 動態聯編 和 靜態聯編 的功能:實現了多態性。     然後我們從最最基本的開始講解。     1.什麼是 聯編?     聯編是指一個計算機程序自身彼此關聯的過程,在這個聯編過程中,需要確定程序中的操作調用(函數調用)與執行該操作(函數)的代碼段之間的映射關系;按照聯編所進行的階段不同,可分為靜態聯編和動態聯編;   仔細讀讀紅色字體的那部分句子。我們就能很清楚的明白什麼是聯編了。給大家舉個最通俗易懂的例子好了:   A類中有fun這個函數, B類中也有fun這個函數,現在我在類外的main函數裡面調用fun 函數。   那麼main函數就是函數調用,調用fun函數,   而A類中的fun函數和B類中的fun函數就是執行該操作的代碼段   所以現在聯編就是實現兩者的映射關系。   [cpp]  class A   {          void func() {cout<<"It's A"<<endl;      };      class B   {          void func() {cout<<"It's B"<<endl;      };   int main()   {       func();   }   聯編就是決定將main函數中的func()的函數調用映射到A中的func函數還是B中的func函數的過程。     2.靜態聯編 和 動態聯編 的定義   知道了什麼事聯編,那麼再來理解動態聯編 和靜態聯編也就不難了     靜態聯編: 是指聯編工作是在程序編譯連接階段進行的,這種聯編又稱為早期聯編;因為這種聯編是在程序開始運行之前完成的; 在程序編譯階段進行的這種聯編又稱靜態束定;在編譯時就解決了程序中的操作調用與執行該操作代碼間的關系,確定這種關系又被稱為束定;編譯時束定又稱為靜態束定;   拿上面的例子來說,靜態聯編就是在編譯的時候就決定了main函數中調用的是A中的func還是B中的func。一旦編譯完成,那麼他們的映射關系就唯一確定了。       動態聯編: 編譯程序在編譯階段並不能確切地知道將要調用的函數,只有在程序執行時才能確定將要調用的函數,為此要確切地知道將要調用的函數,要求聯編工作在程序運行時進行,這種在程序運行時進行的聯編工作被稱為動態聯編,或動態束定,又叫晚期聯編;C++規定:動態聯編是在虛函數的支持下實現的;   動態聯編在編譯的時候還是不知道到底應該選擇哪個func函數,只有在真正執行的時候,它才確定。       靜態聯編和動態聯編都是屬於多態性的,它們是在不同的階段進對不同的實現進行不同的選擇;   其實多態性的本質就是選擇。因為存在著很多選擇,所以就有了多態。       3.靜態聯編   首先還是拿個例子來說事吧。   [cpp]   #include <iostream>   using namespace std;   class shape{     public:       void draw(){cout<<"I am shape"<<endl;}       void fun(){draw();}   };   class circle:public shape{     public:       void draw(){cout<<"I am circle"<<endl;}   };   void main(){       circle  oneshape;       oneshape.fun();   }   現在我們詳細具體定義了一開始的A類和B類以及func函數。讓我們來分析一下: 調用oneshape.fun()的時候,進入類shape中的fun函數。   現在我們的問題就是:fun函數調用的draw到底是shape裡面的draw還是circle中的draw??   答案是:它調用了cshape這個基類的draw函數。所以輸出了 I am shape   那麼一直困擾我的問題是:為什麼調用基類的draw而不是派生類中得draw呢?   書上好像沒有具體講,上課的時候老師那也根本不會講。   自己想了一下,應該可以從匯編的角度理解:   1.調用oneshape.fun(),這裡是一個跳轉指令,進入類shape中的fun函數所在的代碼段   2.類shape的代碼段是依次順序放置的。進入fun函數後,現在我們要調用draw的地址。   由於沒有另外的數據結構來保存draw的地址,所以程序所知道的,必然只有在shape類中的draw地址了,僅僅用一個跳轉指令 在我的vs2010的反匯編調試窗口下是這樣的一句代碼: 013B1546  call        shape::draw (13B10F5h)    很明確這裡指出了shape::draw,也就確定了映射關系,完成了聯編。     4.動態聯編   從第3節中我們看到了靜態聯編的不足(大概教材中就是這樣說的)。 剛才我講了,   由於沒有另外的數據結構來保存draw的地址,所以程序所知道的,必然之後在shape類中的draw地址了,僅僅用一個跳轉指令 www.2cto.com 所以我們想實現動態編聯,其實就是想弄出個數據結構來,這個數據結構用來存放 映射關系,也就是聯編。 所以c++才搞了個虛函數。其實虛函數的本質就是搞了個數據結構。也就是虛函數表 關於虛函數表,自己不想多扯了,扯出來又是一大串,大家自己去下面的博客學習吧。 http://blog.csdn.net/haoel/article/details/1948051/   4.1虛函數   大家都不喜歡理解概念。我也不喜歡。但是概念畢竟是要緊的。我已經跟大家說明了為什麼要引入虛函數。所以接下來請大家耐心看看下面這段概念吧: 虛函數是動態聯編的基礎;虛函數是成員函數,而且是非靜態的成員函數;虛函數在派生類中可能有不同的實現,當使用這個成員函數操作指針或引用所標識的對象時,對該成員函數的調用采用動態聯編方式,即:在程序運行時進行關聯或束定調用關系; 動態聯編只能通過指針或引用標識對象來操作虛函數;如果采用一般的標識對象來操作虛函數,將采用靜態聯編的方式調用虛函數; 如果一個類具有虛函數,那麼編譯器就會為這個類的對象定義一個指針成員,並讓這個指針成員指向一個表格,這個表格裡面存放的是類的虛函數的入口地址;比如:一個基類裡面有一些虛函數,那麼這個基類就擁有這樣一個表,它裡面存放了自己的虛函數的入口地址,其派生類繼承了這個虛函數表,如果在派生類中重寫/覆蓋/修改了基類中的虛函數,那麼編譯器就會把虛函數表中的函數入口地址修改成派生類中的對應虛函數的入口地址;這就為類的多態性的實現提供了基礎;   虛函數按照其聲明順序存放於虛函數表中; 父類的虛函數存放在子類虛函數的前面; 多繼承中,每個父類都有自己的虛函數表; 子類的成員函數被存放於第一個父類的虛函數表中;     4.2動態聯編舉例   最後給大家舉個例子,在第三節的那個例子上,下面的代碼僅僅是多了一個詞:virtual     [cpp]   #include <iostream>   using namespace std;   class shape{     public:       void virtual draw(){cout<<"I am shape"<<endl;}//這裡設定了draw是虛函數       void fun(){draw();}   };   class circle:public shape{     public:       void draw(){cout<<"I am circle"<<endl;}//雖然沒有說明circle類中的draw是虛函數,但是circle其實繼承了virtual性質   };   void main(){       circle  oneshape;       oneshape.fun();   }   現在我們再來運行一下程序,輸出就變成了I am circle 這裡的反匯編代碼貼出來,自己有空看看:     [plain]   00CC15A0  push        ebp     00CC15A1  mov         ebp,esp     00CC15A3  sub         esp,0CCh     00CC15A9  push        ebx     00CC15AA  push        esi     00CC15AB  push        edi     00CC15AC  push        ecx     00CC15AD  lea         edi,[ebp-0CCh]     00CC15B3  mov         ecx,33h     00CC15B8  mov         eax,0CCCCCCCCh     00CC15BD  rep stos    dword ptr es:[edi]     00CC15BF  pop         ecx     00CC15C0  mov         dword ptr [ebp-8],ecx     00CC15C3  mov         eax,dword ptr [this]     00CC15C6  mov         edx,dword ptr [eax]     00CC15C8  mov         esi,esp     00CC15CA  mov         ecx,dword ptr [this]     00CC15CD  mov         eax,dword ptr [edx]     00CC15CF  call        eax     00CC15D1  cmp         esi,esp     00CC15D3  call        @ILT+440(__RTC_CheckEsp) (0CC11BDh)     00CC15D8  pop         edi     00CC15D9  pop         esi     00CC15DA  pop         ebx     00CC15DB  add         esp,0CCh     00CC15E1  cmp         ebp,esp     00CC15E3  call        @ILT+440(__RTC_CheckEsp) (0CC11BDh)     00CC15E8  mov         esp,ebp     00CC15EA  pop         ebp     00CC15EB  ret     等自己看了虛函數表之後再來好好理解下。  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved