歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C++的多態如何在編譯和運行期實現

多態是什麼?簡單來說,就是某段程序調用了一個API接口,但是這個API有許多種實現,根據上下文的不同,調用這段API的程序,會調用該API的不同實現。今天我們只關注繼承關系下的多態。

還是得通過一個例子來看看C++是怎樣在編譯期和運行期來實現多態的。很簡單,定義了一個Father類,它有一個testVFunc虛函數喲。再定義了一個繼承Father的Child類,它重新實現了testVFunc函數,當然,它也學習Father定義了普通的成員函數testFunc。大家猜猜程序的輸出是什麼?

[cpp]
  1. #include <iostream>   
  2. using namespace std;  
  3.   
  4. class Father  
  5. {  
  6. public:  
  7.     int m_fMember;  
  8.   
  9.     void testFunc(){  
  10.         cout<<"Father testFunc "<<m_fMember<<endl;  
  11.     }  
  12.     virtual void testVFunc(){  
  13.         cout<<"Father testVFunc "<<m_fMember<<endl;  
  14.     }  
  15.     Father(){m_fMember=1;}  
  16. };  
  17.   
  18. class Child : public Father{  
  19. public:  
  20.     int m_cMember;  
  21.     Child(){m_cMember=2;}  
  22.       
  23.     virtual void testVFunc(){cout<<"Child testVFunc "<<m_cMember<<":"<<m_fMember<<endl;}  
  24.     void testFunc(){cout<<"Child testFunc "<<m_cMember<<":"<<m_fMember<<endl;}  
  25.     void testNFunc(){cout<<"Child testNFunc "<<m_cMember<<":"<<m_fMember<<endl;}  
  26. };  
  27.   
  28.   
  29. int main()  
  30. {  
  31.     Father* pRealFather = new Father();  
  32.     Child* pFalseChild = (Child*)pRealFather;  
  33.     Father* pFalseFather = new Child();  
  34.       
  35.     pFalseFather->testFunc();  
  36.     pFalseFather->testVFunc();  
  37.   
  38.     pFalseChild->testFunc();  
  39.     pFalseChild->testVFunc();      
  40.     pFalseChild->testNFunc();      
  41.   
  42.     return 0;  
  43. }  
同樣調用了testFunc和testVfunc,輸出截然不同,這就是多態了。它的g++編譯器輸出結果是:

[cpp]
  1. Father testFunc 1  
  2. Child testVFunc 2:1  
  3. Child testFunc 0:1  
  4. Father testVFunc 1  
  5. Child testNFunc 0:1  
看看main函數裡調用的五個test*Func方法吧,這裡有靜態的多態,也有動態的多態。編譯是靜態的,運行是動態的。以下解釋C++編譯器是怎麼形成上述結果的。


首先讓我們用gcc -S來生成匯編代碼,看看main函數裡是怎麼調用這五個test*Func方法的。

[cpp]
  1.         movl    $16, %edi  
  2.         call    _Znwm   
  3.         movq    %rax, %rbx  
  4.         movq    %rbx, %rdi  
  5.         call    _ZN6FatherC1Ev  
  6.         movq    %rbx, -32(%rbp)  
  7.         movq    -32(%rbp), %rax  
  8.         movq    %rax, -24(%rbp)  
  9.         movl    $16, %edi  
  10.         call    _Znwm   
  11.         movq    %rax, %rbx  
  12.         movq    %rbx, %rdi  
  13.         call    _ZN5ChildC1Ev  
  14.         movq    %rbx, -16(%rbp)  
  15.         movq    -16(%rbp), %rdi  
  16. <span style="color:#ff0000;">        call    _ZN6Father8testFuncEv    本行對應pFalseFather->testFunc();</span>  
  17.         movq    -16(%rbp), %rax  
  18.         movq    (%rax), %rax  
  19.         movq    (%rax), %rax  
  20.         movq    -16(%rbp), %rdi  
  21. <span style="color:#ff0000;">        call    *%rax                                        本行對應pFalseFather->testVFunc();</span>  
  22.         movq    -24(%rbp), %rdi  
  23. <span style="color:#ff0000;">        call    _ZN5Child8testFuncEv     本行對應pFalseChild->testFunc();</span>  
  24.         movq    -24(%rbp), %rax  
  25.         movq    (%rax), %rax  
  26.         movq    (%rax), %rax  
  27.         movq    -24(%rbp), %rdi  
  28. <span style="color:#ff0000;">        call    *%rax                                        本行對應pFalseChild->testVFunc();    </span>  
  29.         movq    -24(%rbp), %rdi  
  30. <span style="color:#ff0000;">        call    _ZN5Child9testNFuncEv        本行對應pFalseChild->testNFunc();    </span>  
  31.         movl    $0, %eax  
  32.         addq    $40, %rsp  
  33.         popq    %rbx  
  34.         leave  

紅色的代碼,就是在依次調用上面5個test*Func。可以看到,第1、3次testFunc調用,其結果已經在編譯出來的匯編語言中定死了,C++代碼都是調用某個對象指針指向的testFunc()函數,輸出結果卻不同,第1次是:Father testFunc 1,第3次是:Child testFunc 0:1,原因何在?在編譯出的匯編語言很明顯,第一次調用的是_ZN6Father8testFuncEv代碼段,第三次調用的是_ZN5Child8testFuncEv代碼段,兩個不同的代碼段!編譯完就已經決定出同一個API用哪種實現,這就是編譯期的多態。

Copyright © Linux教程網 All Rights Reserved