1. 程式人生 > >C++語言特性:建構函式,解構函式,虛擬函式,行內函數,靜態成員函式,過載,覆蓋,隱藏

C++語言特性:建構函式,解構函式,虛擬函式,行內函數,靜態成員函式,過載,覆蓋,隱藏

C++中class預設屬性為private, struct預設屬性為public

建構函式

       1.初始化物件成員;
       2.建立類物件;
       由於虛擬函式是建立在物件的基礎上的,因此建構函式不能宣告為虛擬函式;虛擬函式是在執行的時候才識別,根據具體物件進行動態繫結.
       每個類物件都有一個預設建構函式.當一個物件被在堆上建立的時候,第一步先執行new操作,第二步才會執行建構函式體,因此儘可能不要在建構函式內部動態申請太多的資源,以免引起記憶體洩露.
       詳情如下:
cppBaseClass *base = new cppBaseClass();
012A1FDD  push        0Ch  
012A1FDF  call        operator new (12A11E5h)  
012A1FE4  add         esp,4  
012A1FE7  mov         dword ptr [ebp-11Ch],eax  
012A1FED  mov         dword ptr [ebp-4],0  
012A1FF4  cmp         dword ptr [ebp-11Ch],0  
012A1FFB  je          main+70h (12A2010h)  
012A1FFD  mov         ecx,dword ptr [ebp-11Ch]  
012A2003  call        cppBaseClass::cppBaseClass (12A1023h)  
012A2008  mov         dword ptr [ebp-130h],eax  
012A200E  jmp         main+7Ah (12A201Ah)  
012A2010  mov         dword ptr [ebp-130h],0  
012A201A  mov         eax,dword ptr [ebp-130h]  
012A2020  mov         dword ptr [ebp-128h],eax  
012A2026  mov         dword ptr [ebp-4],0FFFFFFFFh  
012A202D  mov         ecx,dword ptr [ebp-128h]  
012A2033  mov         dword ptr [ebp-14h],ecx  


         拷貝建構函式[複製建構函式]
         如果要動態地為本地C++類的成員分配空間,則必須實現複製建構函式.
         下面是拷貝建構函式的實現模版:
          ConstructorFunctionClassName(const ConstructorFunctionClassName& ObjectType)
          {
          }
          拷貝建構函式的呼叫條件:
          1.當一個物件以值傳遞的方式傳入函式體;
          2.當一個物件以值傳遞的方式作為函式的返回值;
          3.當一個物件通過另外一個物件進行初始化;
          為什麼需要實現拷貝建構函式?

          首先這裡需要描述一下C++中預設拷貝建構函式,參考下面程式碼:

          className Object0("Test the constructor function");
          className Object1 = Object0; //預設建構函式被呼叫
          預設建構函式會把類物件Object0的指標成員儲存地址複製到Object1中,這個時候Object1和Object0同時指向同一塊記憶體區域, 那麼如果Oject1被銷燬了,那麼Object0中的內容就會發生改變.
         上面的現象也被OOP中稱為淺拷貝.
         那對應的肯定有深拷貝, Object0擁有資源,當上面的Object1在複製的過程中重新分配了資源,那麼這個過程就是深拷貝.
        className cName;
        className::className(cName);

        在傳遞物件cName之前,編譯器需要安排建立該物件的副本.因此,編譯器為了處理複製建構函式的這條語句,需要呼叫複製建構函式來建立實參的副本。由於是按值傳遞,第二次呼叫同樣需要建立實參的副本,因此還得呼叫複製建構函式,產生無窮呼叫.
         看下面的一段程式碼:
        className  cName("Test default constructor");
        testDefaultConstructor(cName);
         void testDefaultConstructor( className Object0)
         {
                Object0.PrintString();
         } 

實參cName是作為傳值的方式傳遞的,那麼Object0將導致預設建構函式被呼叫:
         1.建立cName物件;
         2.由於cName是傳值方式,因此導致Object0將要建立一個副本,而且該副本同時指向原來物件所指向的資料成員.
         3.當退出testDefaultConstructor函式的時候, Object0超出了其作用域,那麼Object0的解構函式將要被呼叫,Object0指向的資料成員將要被釋放.
         4.當函式從testDefaultConstructor返回的時候, cName物件依然指向之前Object0指向的資料區域.異常發生!

PS:

當類物件中有指標成員時必須用深拷貝,淺拷貝會導致指標成員變成野指標的風險;

         傳值的時候,物件的副本產生,但是該副本和原始物件都包含了指標成員,同時該指標成員指向的地址是相同的,那麼在完成了傳值的作用域之後,該副本物件會自動銷燬,因此必然導致該副本的解構函式被呼叫[通常會在解構函式中釋放指標成員的資源],那麼當真正的物件被習夠的時候,就會再次處理該指標成員.

解構函式:

       1.給物件提供釋放資源的機會;
        2.銷燬物件,銷燬不再需要或超出其作用域的物件.當物件超出其作用域時,程式將自動呼叫類的解構函式.
        如果想要防止對像被建立在堆上,可以私有化解構函式,不過這樣以來該類不能被繼承.
        如果需要呼叫私有解構函式,則需要實現一個成員函式,在該成員函式內部呼叫
delete this;

虛擬解構函式:
       如果當前類作為介面基類,那麼需要宣告該類的解構函式為虛擬解構函式.
       因為如果,當我們使用基類的指標去刪除派生類的物件的時候,如果基類的解構函式不是虛擬解構函式,那麼派生類的解構函式將會不被執行.
       但是當生命了虛擬函式之後,在物件中就會生成一個虛擬函式表,這樣會增加類物件的儲存空間開銷.
       下面部分是基於基類虛擬解構函式的條件下反彙編出來的:
cppBaseClass *base = new cppDeriveClass();
002D235D  push        10h  
002D235F  call        operator new (2D121Ch)  
002D2364  add         esp,4  
002D2367  mov         dword ptr [ebp-104h],eax  
002D236D  mov         dword ptr [ebp-4],0  
002D2374  cmp         dword ptr [ebp-104h],0  
002D237B  je          main+70h (2D2390h)  
002D237D  mov         ecx,dword ptr [ebp-104h]  
002D2383  call        cppDeriveClass::cppDeriveClass (2D1069h)  
002D2388  mov         dword ptr [ebp-118h],eax  
002D238E  jmp         main+7Ah (2D239Ah)  
002D2390  mov         dword ptr [ebp-118h],0  
002D239A  mov         eax,dword ptr [ebp-118h]  
002D23A0  mov         dword ptr [ebp-110h],eax  
002D23A6  mov         dword ptr [ebp-4],0FFFFFFFFh  
002D23AD  mov         ecx,dword ptr [ebp-110h]  
002D23B3  mov         dword ptr [ebp-14h],ecx  
delete base;
002D23B6  mov         eax,dword ptr [ebp-14h]  
002D23B9  mov         dword ptr [ebp-0ECh],eax  
002D23BF  mov         ecx,dword ptr [ebp-0ECh]  
002D23C5  mov         dword ptr [ebp-0F8h],ecx  
002D23CB  cmp         dword ptr [ebp-0F8h],0  
002D23D2  je          main+0D9h (2D23F9h)  
002D23D4  mov         esi,esp  
002D23D6  push        1  
002D23D8  mov         edx,dword ptr [ebp-0F8h]  
002D23DE  mov         eax,dword ptr [edx]  
002D23E0  mov         ecx,dword ptr [ebp-0F8h]  
002D23E6  mov         edx,dword ptr [eax]  
002D23E8  call        edx  
002D23EA  cmp         esi,esp  
002D23EC  call        @ILT+445(__RTC_CheckEsp) (2D11C2h)  
002D23F1  mov         dword ptr [ebp-118h],eax  
002D23F7  jmp         main+0E3h (2D2403h)  
002D23F9  mov         dword ptr [ebp-118h],0  
return 0;
002D2403  xor         eax,eax  
}

虛擬函式:

        虛擬函式主要是實現了面向物件中的多型的作用.通俗的講就是通過基類的指標指向派生類的物件,用基類的指標來呼叫派生類的成員函式. 這種技術可以讓派生類擁有“多種形態”,這是一種泛型技術, 通過使用不變的程式碼來實現可變的演算法.比如:模版技術, RTTI[Run-Time Type Identification], 虛擬函式技術.
        虛擬函式是以virtual關鍵字宣告的基類函式.如果在基類中將某個函式指定為virtual,並且派生類中有該函式的另外一個定義,則編譯器將知道我們不想靜態連結該函式.
        當基類中聲明瞭虛擬函式,那麼根據呼叫該函式的當前物件的型別,選擇派生類中出現的該函式的其他定義.
        純虛擬函式, 通過在函式宣告最後新增=0,可以將本地C++基類中的虛擬函式定義為純虛擬函式. 那麼該類就稱為不能建立任何物件的抽象類,在任何派生類中,都必須定義所有純虛擬函式.如果不是,則該派生類也將稱為抽象類.
       根據虛擬函式定義後的虛擬函式表,基類指標既可以指向派生類的成員,也可以指向基類的成員.
        虛擬函式表,
        下面部分內容參考陳皓專欄

        http://blog.csdn.net/haoel/article/details/1948051

cppVirtualFunctionTable.cpp

#include <iostream>
using std::cout;
using std::endl;

class baseClass
{
public:
	virtual void a(){ cout<<"baseClass function a()"<<endl;}
	virtual void b(){ cout<<"baseClass function b()"<<endl;}
	virtual void c(){ cout<<"baseClass function c()"<<endl;}
};

class deriveClass: public baseClass
{
public:
	void a(){ cout<<"deriveClass function a()"<<endl;}
	void b1(){ cout<<"deriveClass function b1()"<<endl;}
	void c1(){ cout<<"deriveClass function c1()"<<endl;}
};

main.cpp

#include "cppVirtualFunctionTable.cpp"
int main(int argc, char** argv)
{
     // define the Func is an alias for the function pointer.
     typedef void(*Func)(void);

     baseClass cBase;
     Func pFunc = NULL;
     cout<<"虛擬函式表地址:"<<(int*)(&cBase)<<endl;
     cout<<"虛擬函式表第一個函式地址:"<<(int*)*(int*)(&cBase)<<endl;

     // Invoke first virtual function
     pFunc = (Func)*((int*)*(int*)(&cBase));
     pFunc();
     return 0;
}


環境:Microsoft Visual Studio 2010, Window 7

輸出結果
虛擬函式表地址:0031fce4
虛擬函式表第一個函式地址:011e7868
baseClass function a()
請按任意鍵繼續. . .

下面是虛擬函式表的詳細情況:

圖1
(int*)(&cBase) //強制轉換cBase物件在記憶體中的內容為int*[0x0031fce4],它指向cBase物件的第一個成員的地址,即虛擬函式表的地址[0x011e7868]
(int*)*(int*)(&cBase) //對虛擬函式表的地址進行解引用,*(int*)(&cBase)指向的地址就是虛擬函式表的地址,然後轉換為int*,它指向虛擬函式表的第一個成員,即_vfptr[0] ,地址為[0x011e121c]

下面是關於基類地址,以及虛擬函式表、基類成員、派生類成員的詳細分佈:


圖2

從上面的圖中我們可以看到,派生類的成員a()覆蓋了基類的成員a()
C++中的過載、覆蓋、隱藏
1.過載從最簡單的角度來講只發生在物件內部,物件內部同名的函式,但是引數個數或引數型別不同;

2.覆蓋就是上面圖中標示的那種情況;

3.當派生類和基類的函式同名,而且基類同名函式前virtual修飾符,基類的同名函式被隱藏;

“隱藏”是指派生類的函式遮蔽了與其同名的基類函式,規則如下:
(1)如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無 virtual 關鍵字,基類的函式將被隱藏(注意別與過載混淆) 。
(2)如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有 virtual關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆) 。

行內函數:
        關鍵字inline,功能類似巨集替換,具有函式的結構,在編譯時刻根據函式名來替換成對應的程式碼,在程式碼量小的重複次數多的情況下,比較高效.不是適合複雜程式碼,同時是否能夠實現內聯的功能,具體要依賴編譯器,有可能編譯器根據實際情況當成普通函式來處理.
靜態成員函式:
        靜態成員在同類物件中只有一個例項,因此可以用來統計同類物件的計數;
        靜態成員函式獨立於類物件,因此即使類物件不存在,靜態成員函式依然可以被呼叫,靜態成員函式只能呼叫靜態成員變數在這樣的情況下.

       例項:Singleton模式,保證一個類只有一個例項物件,同時使該例項只有一個全域性訪問點.

C++物件屬性:public, protected, private

        共有繼承,基類的公共成員和保護成員可以作為其派生類的公共成員和保護成員.派生類無法訪問基類的私有成員.

        私有繼承,基類的共有成員和私有成員都作為其派生類的私有成員.基類的成員只能由直接派生類來訪問,不能再往下繼承。

         保護繼承,基類的共有成員和保護成員都作為其派生類的保護成員,基類的共有成員和保護成員只能被其派生類成員和友元函式訪問.

相關推薦

C++語言特性建構函式,函式,虛擬函式,,靜態成員函式,過載覆蓋隱藏

C++中class預設屬性為private, struct預設屬性為public 建構函式:        1.初始化物件成員;        2.建立類物件;        由於虛擬函式是建立在物件的基礎上的,因此建構函式不能宣告為虛擬函式;虛擬函式是在執行的時候才識別,

4、【C++】靜態成員變數/靜態成員函式//友元函式/友元類/友元成員函式

一、靜態成員     我們可以使用 static 關鍵字來把類成員定義為靜態的。當我們宣告類的成員為靜態時,這意味著無論建立多少個類的物件,靜態成員都只有一個副本。     靜態成員在類的所有物件中是共享的。如果不存在其他的初始化語句,在建立第一個物件時,所有的靜態資料都會被初始化為

C/C++之巨集、和普通函式的區別

轉載:https://www.cnblogs.com/ht-927/p/4726570.html C/C++之巨集、行內函數和普通函式的區別 行內函數的執行過程與帶引數巨集定義很相似,但引數的處理不同。帶引數的巨集定義並不對引數進行運算,而是直接替換;行內函數首先是函式,這就意味著函式的很多

c++ 和constexper函式

行內函數 將函式宣告行內函數, 通常是在編譯器, 將它在呼叫點將函式“內聯展開”。 inline int len(const string& str) { return st

與普通函式有什麼區別

轉載 2007年12月25日 14:32:00 xiaoyan_cug 閱讀數:796  行內函數和普通函式相比可以加快程式的執行的速度,因為在呼叫行內函數時,不需要中斷,在編譯的時候直接將行內函數鑲嵌到目的碼中。內聯是以增加空間消耗為代價,換取時間開銷。巨集只是一種簡單

與巨集函式的區別

行內函數 概念 以inline修飾的函式叫做行內函數,編譯時C++編譯器會在呼叫行內函數的地方展開,沒有函式壓棧的開銷,行內函數提升程式執行的效率 特性 inline

、模板函式 之於標頭檔案

本文轉自http://blog.csdn.net/cyphei/article/details/7319826 一、基本說明 C++標準中提到,一個編譯單元[translationunit]是指一個.cpp檔案以及它所include的所有.h檔案,.h檔案裡的程式碼

c++——函式特性函式過載簡單解釋

有預設引數值的引數必須在引數表的最右端 正確示例 void fun(int i;int j=1;int k=10); 錯誤示例 void fun(int i;int j=1;int k); 一般編譯器通過率高的是: 宣告寫預設值;定義不寫預設值 如下示例:

個人C++速成筆記(1) -- C++與C不一樣的地方、預設引數、函式過載函式模板、庫函式的呼叫

之前學過C,現在想稍微學習下C++,由於上班,只能利用平時的空閒時間學習,記錄一下學習歷程,激勵自己有始有終,部落格內容主要記錄C與C++不同的地方。                    

C++關鍵字、名稱空間、函式過載、預設引數、、引用

一 .C++入門 1.C++關鍵字 2.名稱空間 3.C++輸入&輸出 4.預設引數 5.函式過載 6.引用 7.行內函數 8.auto關鍵字 9.基於範圍的for迴圈 10.指標空值nullptr&nullptr_t 二. 正文 1.C++關鍵字(C++98)   

Python函式細節多數量引數、強制引數傳遞、返回多值、匿名/

1. 可接受任意數量引數的函式 接受任意數量的位置引數,使用引數*來解決 # rest是由所有其他位置引數組成的元組 def avg(first, *rest): return ( first + sum(rest) ) / (1+len(rest)) pri

C語言 inline與帶參巨集

C語言 inline行內函數與帶參巨集 一、簡述         簡單的介紹inline行內函數、帶參巨集的作用。 二、函式的執行與呼叫         函式執行:會將之前的棧的頂,棧基址壓棧,並在棧中開

c/c++區別(一)函式的預設值 函式過載 c/c++介面呼叫 const在c/c++的區別

c/c++ 的區別 一.函式的預設值 在C語言裡函式的引數是不能夠帶預設值的。比如int func(int a, int b = 1);這樣的宣告就是不正確的。但是在C++中上述的宣告是被允許的   函式的預設引數值,即在定義引數的時候同時給它一個初始值。在呼叫函式的時候,

C++:名稱空間、預設引數、函式過載、引用、

一.名稱空間           在C/C++中,變數、函式和類都是大量存在的,這些變數、函式和類的名稱都將作用於全域性作用域中,可能會導致很多衝突,所以我們就選擇使用名稱空間。         &nb

C++】詳(inline)

前言 最近在學習C++的時候,行內函數讓我很迷糊,上網查閱了很多的資料,發現裡邊解釋的很抽象,最後在B站裡將行內函數理解了!如果你想要搞懂行內函數,那麼一定要好好看看此篇部落格! 1、什麼是行內函數 行內函數(有時稱作線上函式或編譯時期展開函式)是一種程式語言結構,用來建議編

c++

我們知道,函式封裝呼叫有利於程式碼的重複利用,因為我們可以函式起一個通俗易懂的名字,因此閱讀和理解函式通常比讀懂等價的條件表示式容易的多。 然而函式相較於等價表示式執行速度要慢一些,因為在大多數機器上,一次函式呼叫意味著其實包含一系列的工作:呼叫前先儲存暫存器,並在返回時恢復;可能需

c++中函式的引數傳遞和預設實參的理解

1.引數傳遞   1)函式呼叫時,c++中有三種傳遞方法:值傳遞、指標傳遞、引用傳遞。 給函式傳遞引數,遵循變數初始化規則。非引用型別的形參一相應的實參的副本初始化。對(非引用)形參的任何修改僅作用域區域性副本,並不影響實參本身。 為了避免傳遞副本的開銷,可將形參指定為引用型別。對引用形參的

c++中普通函式的區別

我們都知道編譯的最終產品是可執行程式——— 由一組由機器語言指令組成,在執行程式時,作業系統將這些指令載入到計算機記憶體中。因此,每一組指令都有一個特定的記憶體地址。 一.普通函式的呼叫 a.當代碼執

C++巨集普通函式的執行速度以及三者的差異

#include <boost/timer.hpp>#define  _SUM(x,y) x+yusing std::cout;using std::endl;using boost::timer;const int MAX_ARR_SIZE = 50000;i

C++的過載函式

行內函數行內函數是C++語言為降低小程式用開銷而採取的一種機制。定義行內函數的方法是,在函式名第一次出現時,在函式名之前冠以關鍵字inline。通常在函式原型中指定。若已在函式原型中指定inline,則函式定義時不能重複給出。行內函數原型為:inline 型別 函式名(形式引