1. 程式人生 > >C++ 虛擬函式 過載 重寫的區別(轉)

C++ 虛擬函式 過載 重寫的區別(轉)

 C++程式語言是一款應用廣泛,支援多種程式設計的計算機程式語言。我們今天就會為大家詳細介紹其中C++多型性的一些基本知識,以方便大家在學習過程中對此能夠有一個充分的掌握。

  多型性可以簡單地概括為“一個介面,多種方法”,程式在執行時才決定呼叫的函式,它是面向物件程式設計領域的核心概念。多型(polymorphism),字面意思多種形狀。
  C++多型性是通過虛擬函式來實現的,虛擬函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這裡我覺得要補充,重寫的話可以有兩種,直接重寫成員函式和重寫虛擬函式,只有重寫了虛擬函式的才能算作是體現了C++多型性)而過載則是允許有多個同名的函式,而這些函式的引數列表不同,允許引數個數不同,引數型別不同,或者兩者都不同。編譯器會根據這些函式的不同列表,將同名的函式的名稱做修飾,從而生成一些不同名稱的預處理函式,來實現同名函式呼叫時的過載問題。但這並沒有體現多型性。
  多型與非多型的實質區別就是函式地址是早繫結還是晚繫結。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫地址,並生產程式碼,是靜態的,就是說地址是早繫結的。而如果函式呼叫的地址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。

  那麼多型的作用是什麼呢,封裝可以使得程式碼模組化,繼承可以擴充套件已存在的程式碼,他們的目的都是為了程式碼重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同一個介面呼叫到適應各自物件的實現方法。

  最常見的用法就是宣告基類的指標,利用該指標指向任意一個子類物件,呼叫相應的虛擬函式,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛擬函式的話,即沒有利用C++多型性,則利用基類指標呼叫相應的函式的時候,將總被限制在基類函式本身,而無法呼叫到子類中被重寫過的函式。因為沒有多型性,函式呼叫的地址將是一定的,而固定的地址將始終呼叫到同一個函式,這就無法實現一個介面,多種方法的目的了。

筆試題目:

  1. #include<iostream>
  2. usingnamespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     void foo()  
  7.     {  
  8.         printf("1\n");  
  9.     }  
  10.     virtualvoid fun()  
  11.     {  
  12.         printf("2\n");  
  13.     }  
  14. };  
  15. class B : public A  
  16. {  
  17. public:  
  18.     void foo()  
  19.     {  
  20.         printf("3\n");  
  21.     }  
  22.     void fun()  
  23.     {  
  24.         printf("4\n");  
  25.     }  
  26. };  
  27. int main(void)  
  28. {  
  29.     A a;  
  30.     B b;  
  31.     A *p = &a;  
  32.     p->foo();  
  33.     p->fun();  
  34.     p = &b;  
  35.     p->foo();  
  36.     p->fun();  
  37.     return 0;  
  38. }  
     第一個p->foo()和p->fuu()都很好理解,本身是基類指標,指向的又是基類物件,呼叫的都是基類本身的函式,因此輸出結果就是1、2。
    第二個輸出結果就是1、4。p->foo()和p->fuu()則是基類指標指向子類物件,正式體現多型的用法,p->foo()由於指標是個基類指標,指向是一個固定偏移量的函式,因此此時指向的就只能是基類的foo()函式的程式碼了,因此輸出的結果還是1。而p->fun()指標是基類指標,指向的fun是一個虛擬函式,由於每個虛擬函式都有一個虛擬函式列表,此時p呼叫fun()並不是直接呼叫函式,而是通過虛擬函式列表找到相應的函式的地址,因此根據指向的物件不同,函式地址也將不同,這裡將找到對應的子類的fun()函式的地址,因此輸出的結果也會是子類的結果4。
  筆試的題目中還有一個另類測試方法。即

       B *ptr = (B *)&a;  ptr->foo();  ptr->fun();
  問這兩呼叫的輸出結果。這是一個用子類的指標去指向一個強制轉換為子類地址的基類物件。結果,這兩句呼叫的輸出結果是3,2。
  並不是很理解這種用法,從原理上來解釋,由於B是子類指標,雖然被賦予了基類物件地址,但是ptr->foo()在呼叫的時候,由於地址偏移量固定,偏移量是子類物件的偏移量,於是即使在指向了一個基類物件的情況下,還是呼叫到了子類的函式,雖然可能從始到終都沒有子類物件的例項化出現。
  而ptr->fun()的呼叫,可能還是因為C++多型性的原因,由於指向的是一個基類物件,通過虛擬函式列表的引用,找到了基類中fun()函式的地址,因此呼叫了基類的函式。由此可見多型性的強大,可以適應各種變化,不論指標是基類的還是子類的,都能找到正確的實現方法。
  1. //小結:1、有virtual才可能發生多型現象
  2. // 2、不發生多型(無virtual)呼叫就按原型別呼叫
  3. #include<iostream>
  4. usingnamespace std;  
  5. class Base  
  6. {  
  7. public:  
  8.     virtualvoid f(float x)  
  9.     {  
  10.         cout<<"Base::f(float)"<< x <<endl;  
  11.     }  
  12.     void g(float x)  
  13.     {  
  14.         cout<<"Base::g(float)"<< x <<endl;  
  15.     }  
  16.     void h(float x)  
  17.     {  
  18.         cout<<"Base::h(float)"<< x <<endl;  
  19.     }  
  20. };  
  21. class Derived : public Base  
  22. {  
  23. public:  
  24.     virtualvoid f(float x)  
  25.     {  
  26.         cout<<"Derived::f(float)"<< x <<endl;   //多型、覆蓋
  27.     }  
  28.     void g(int x)  
  29.     {  
  30.         cout<<"Derived::g(int)"<< x <<endl;     //隱藏
  31.     }  
  32.     void h(float x)  
  33.     {  
  34.         cout<<"Derived::h(float)"<< x <<endl;   //隱藏
  35.     }  
  36. };  
  37. int main(void)  
  38. {  
  39.     Derived d;  
  40.     Base *pb = &d;  
  41.     Derived *pd = &d;  
  42.     // Good : behavior depends solely on type of the object
  43.     pb->f(3.14f);   // Derived::f(float) 3.14
  44.     pd->f(3.14f);   // Derived::f(float) 3.14
  45.     // Bad : behavior depends on type of the pointer
  46.     pb->g(3.14f);   // Base::g(float)  3.14
  47.     pd->g(3.14f);   // Derived::g(int) 3 
  48.     // Bad : behavior depends on type of the pointer
  49.     pb->h(3.14f);   // Base::h(float) 3.14
  50.     pd->h(3.14f);   // Derived::h(float) 3.14
  51.     return 0;  
  52. }  
令人迷惑的隱藏規則
本來僅僅區別過載與覆蓋並不算困難,但是C++的隱藏規則使問題複雜性陡然增加。
這裡“隱藏”是指派生類的函式遮蔽了與其同名的基類函式,規則如下:
(1)如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual
關鍵字,基類的函式將被隱藏(注意別與過載混淆)。
(2)如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual
關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆)。
上面的程式中:
(1)函式Derived::f(float)覆蓋了Base::f(float)。
(2)函式Derived::g(int)隱藏了Base::g(float),而不是過載。
(3)函式Derived::h(float)隱藏了Base::h(float),而不是覆蓋。


C++純虛擬函式
 一、定義
  純虛擬函式是在基類中宣告的虛擬函式,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛擬函式的方法是在函式原型後加“=0” 
  virtual void funtion()=0 
二、引入原因
   1、為了方便使用多型特性,我們常常需要在基類中定義虛擬函式。 
   2、在很多情況下,基類本身生成物件是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成物件明顯不合常理。 
  為了解決上述問題,引入了純虛擬函式的概念,將函式定義為純虛擬函式(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多型性。同時含有純虛擬函式的類稱為抽象類,它不能生成物件。這樣就很好地解決了上述兩個問題。
三、相似概念
   1、多型性 
  指相同物件收到不同訊息或不同物件收到相同訊息時產生不同的實現動作。C++支援兩種多型性:編譯時多型性,執行時多型性。 
  a、編譯時多型性:通過過載函式實現 
  b、執行時多型性:通過虛擬函式實現。 

  2、虛擬函式 
  虛擬函式是在基類中被宣告為virtual,並在派生類中重新定義的成員函式,可實現成員函式的動態覆蓋(Override)
  3、抽象類 
  包含純虛擬函式的類稱為抽象類。由於抽象類包含了沒有定義的純虛擬函式,所以不能定義抽象類的物件

相關推薦

C++ 虛擬函式 過載 重寫區別

 C++程式語言是一款應用廣泛,支援多種程式設計的計算機程式語言。我們今天就會為大家詳細介紹其中C++多型性的一些基本知識,以方便大家在學習過程中對此能夠有一個充分的掌握。   多型性可以簡單地概括為“一個介面,多種方法”,程式在執行時才決定呼叫的函式,它是面向物件程式設

cdecl、stdcall、fastcall、thiscall函式呼叫約定區別

 在C語言中,假設我們有這樣的一個函式:    int function(int a,int b)    呼叫時只要用result = function(1,2)這樣的方式就可以使用這個函式。但是,當高階語言被編譯成計算機可以識別的機器碼時,有一個問題就凸現出來:在CPU中,計算機沒有辦法知道一個函式呼叫需要

.NET、C#和ASP.NET三才之間的區別

編程 tail 基於web 發現 我認 ava 微軟 框架 framwork 經常有同學會在.NET、C#和ASP.NET這三者之間區別不清楚,到底它們之間有什麽聯系呢?在這裏我給大家歸納如下: 1、.NET是一個平臺,一個抽象的平臺的概念。

JVM虛擬機選項:Xms Xmx PermSize MaxPermSize區別

生產 申請 java ane 獨立 use 我們 也會 交換 java雖然是自動回收內存,但是應用程序,尤其服務器程序最好根據業務情況指明內存分配限制。否則可能導致應用程序宕掉。舉例說明含義:-Xms128m表示JVM Heap(堆內存)最小尺寸128MB,初始分配-Xmx

C語言面向物件程式設計:虛擬函式與多型3

 在《 C++ 程式設計思想》一書中對虛擬函式的實現機制有詳細的描述,一般的編譯器通過虛擬函式表,在編譯時插入一段隱藏的程式碼,儲存型別資訊和虛擬函式地址,而在呼叫時,這段隱藏的程式碼可以找到和實際物件一致的虛擬函式實現。     我們在這裡提供

C語言中 .h檔案和.c檔案的區別

要理解.c檔案與.h檔案有什麼不同之處,首先需要弄明白編譯器的工作過程,一般說來編譯器會做以下幾個過程:1.預處理階段2.詞法與語法分析階段3.編譯階段,首先編譯成純彙編語句,再將之彙編成跟CPU相關的二進位制碼,生成各個目標檔案4.連線階段,將各個目標檔案中的各段程式碼進行絕對地址定位,生成跟特定平臺相關

從零開始學C++之虛擬函式與多型虛擬函式表指標、虛解構函式、object slicing與虛擬函式C++物件模型圖

#include <iostream>using namespace std;class CObject {public:     virtual void Serialize()     {         cout << "CObject::Serialize ..." <&

<C++學習二十>C++中函式過載的理解未完待續

摘要: 本篇部落格僅作為筆記,如有侵權,請聯絡,立即刪除(網上找部落格學習,然後手記筆記,因紙質筆記不便儲存,所以儲存到網路筆記)。   我們平時寫程式碼中會用到幾個函式但是他們的實現功能相同,但是有些細節卻不同。例如:交換兩個數的其中包括(int,float,char,double)這些型別。這C語言中我

DSP中兩個延時函式區別

在程式設計的時候可以用CCS自帶的延時函式,在使用時我發現有兩個延時函式都可以用: DELAY_US(1); DSP28x_usDelay(1); 在呼叫DELAY_US(1)這

scanf與gets函式讀取字串的區別

scanf與gets函式讀取字串的區別 1.scanf() 會忽略行開頭的所有空格,並以空格、換行符結束輸入;  使用getchar()讀取scanf語句執行後,緩衝區留下的換行符,  gets讀入以任何字元開始的字串,以換行符結束,但之後會丟棄換行符

C++——建立類的時候用new與不用new 的區別

C++在建立物件的時候可以採用兩種方式:(例如類名為Test) Test test  或者 Test* pTest =

new BigDecimal(0.01) 與 new BigDecimal(String.valueOf(0.01))的區別

賬單 rto sta egerp 存在 調整 nan com mod 轉自:http://blog.csdn.net/major1985/article/details/50210293 一般我們使用BigDecimal進行比較精密的計算,我這裏計算金額。註意使用d

UI/UE/ID/UED/UCD的區別

理論 image 英文 business height 前端設計 過程 lock 主要對象 對於剛剛接觸用戶體驗交互設計的同學來說,很多雲裏霧裏的英文縮寫,分不清各個概念代表著什麽含義,今天給大家做一個簡單地介紹。 簡述: UI (User Interface):用戶

block,inline和inline-block概念和區別

line eight wan pan 排列 isp 我們 .com 是個 總體概念 block和inline這兩個概念是簡略的說法,完整確切的說應該是 block-level elements (塊級元素) 和 inline elements (內聯元素)。block元素

C++11 並發指南系列

flag target ise shared 編程 mutex 指南 sha targe 本系列文章主要介紹 C++11 並發編程,計劃分為 9 章介紹 C++11 的並發和多線程編程,分別如下: C++11 並發指南一(C++11 多線程初探)(本章計劃 1-2 篇,已完

C/C++中作用域詳解

防止 局部作用域 gist 文件中 方式 為什麽不使用 形式參數 lan archive 作用域規則告訴我們一個變量的有效範圍,它在哪兒創建,在哪兒銷毀(也就是說超出了作用域)。變量的有效作用域從它的定義點開始,到和定義變量之前最鄰近的開括號配對的第一個閉括號。也就是說,作

HashMap,LinkedHashMap,TreeMap的區別

很快 value 有一個 連接池 構造 數據 com 最好的 三種      Map主要用於存儲健值對,根據鍵得到值,因此不允許鍵重復(重復了覆蓋了),但允許值重復。 HashMap   HashMap 是一個最常用的Map,它根據鍵的HashCode 值存儲數

TI德州芯片TLV系列和TPS系列芯片區別

系列 s系列 需要 如果 來講 tps 需求 性價比 區別 TLV和TPS一般會有pin to pin的對應型號; 一般來講,TPS精度、準確度和性能會好一些,所以價錢要貴一些; 對應TLV就是一樣可以實現上述功能,但是精度和性能等級是稍微低一點的; 具體選擇原則,這個要看

最小二乘法和最大似然估計的聯系和區別

enc bsp 聯系 角度 tro span nbsp sdn .science 對於最小二乘法,當從模型總體隨機抽取n組樣本觀測值後,最合理的參數估計量應該使得模型能最好地擬合樣本數據,也就是估計值和觀測值之差的平方和最小。而對於最大似然法,當從模型總體隨機抽取n組樣本觀

Linux /bin, /sbin, /usr/bin, /usr/sbin 區別

ifdown httpd config mail dmesg cfdisk 腳本 綜述 com 在linux下我們經常用到的四個應用程序的目錄是:/bin、/sbin、/usr/bin、/usr/sbin bin: bin為binary的簡寫主要放置一些系統的必備執