歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C++的多態如何在編譯和運行期實現

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

日期:2017/3/1 10:34:43   编辑:Linux編程

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

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

[cpp]
  1. #include <iostream>
  2. using namespace std;
  3. class Father
  4. {
  5. public:
  6. int m_fMember;
  7. void testFunc(){
  8. cout<<"Father testFunc "<<m_fMember<<endl;
  9. }
  10. virtual void testVFunc(){
  11. cout<<"Father testVFunc "<<m_fMember<<endl;
  12. }
  13. Father(){m_fMember=1;}
  14. };
  15. class Child : public Father{
  16. public:
  17. int m_cMember;
  18. Child(){m_cMember=2;}
  19. virtual void testVFunc(){cout<<"Child testVFunc "<<m_cMember<<":"<<m_fMember<<endl;}
  20. void testFunc(){cout<<"Child testFunc "<<m_cMember<<":"<<m_fMember<<endl;}
  21. void testNFunc(){cout<<"Child testNFunc "<<m_cMember<<":"<<m_fMember<<endl;}
  22. };
  23. int main()
  24. {
  25. Father* pRealFather = new Father();
  26. Child* pFalseChild = (Child*)pRealFather;
  27. Father* pFalseFather = new Child();
  28. pFalseFather->testFunc();
  29. pFalseFather->testVFunc();
  30. pFalseChild->testFunc();
  31. pFalseChild->testVFunc();
  32. pFalseChild->testNFunc();
  33. return 0;
  34. }
同樣調用了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