1. 程式人生 > >C++精進篇(八)之―函式重定義、過載、重寫、重入

C++精進篇(八)之―函式重定義、過載、重寫、重入

 C++函式重定義、過載、重寫、重入

1. 重寫 (override):

       父類與子類之間的多型性。子類重新定義父類中有相同名稱和引數的虛擬函式。

       函式重寫的特點:

(1)不同的範圍,分別位於基類和派生類中

(2)重寫函式必須有相同的型別,名稱和引數列表 (即相同的函式原型)

(3)重寫函式的訪問修飾符可以不同。儘管 virtual 是 private 的,派生類中重寫改寫為 public,protected 也是可以的

(4)被重寫的函式不能是 static 的。基類函式必須有virtual關鍵字 ( 即函式在最原始的基類中被宣告為 virtual ) 。

      特殊情況:若派生類重寫的虛擬函式屬於一個過載版本,則該重寫的函式會隱藏基類中與虛擬函式同名的其他函式。

      作用效果:父類的指標或引用根據傳遞給它的子類地址或引用,動態地呼叫屬於子類的該函式。這個晚繫結過程只對virtual函式起作用。具體原理是由虛擬函式表(VTABLE)決定的。

2. 過載 (overload):

       指函式名相同,但是它的引數表列個數或順序,型別不同。但是不能靠返回型別來判斷。

       函式過載的特點:

(1)相同的範圍(在同一個類中)

(2)函式名字相同

(3)形參列表不同(可能是引數個數  or  型別  or  順序不同),返回值無要求 

      特殊情況:若某一個過載版本的函式前面有virtual修飾,則表示它是虛擬函式。但它也是屬於過載的一個版本

     不同的建構函式(無參構造、有參構造、拷貝構造)是過載的應用

     作用效果和原理:編譯器根據函式不同的引數表,將函式體與函式呼叫進行早繫結。過載與多型無關,只是一種語言特性,與面向物件無關。

(4)virtual關鍵字可有可無

3. 重定義 (redefining):

       子類重新定義父類中有相同名稱的非虛擬函式 ,重定義(隱藏)指派生類的函式遮蔽了與其同名的基類函式( 引數列表可以不同 ) 。

(1)如果派生類的函式和基類的函式同名,但是引數不同,此時,不管有無virtual,基類的函式被隱藏。

(2)如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有vitual關鍵字,此時,基類的函式被隱藏。

       函式重定義的特點:

(1)不在同一個作用域(分別位於基類、派生類) 

(2)函式的名字必須相同 

(3)對函式的返回值、形參列表無要求

     特殊情況:若派生類定義的該函式與基類的成員函式完全一樣(返回值、形參列表均相同),且基類的該函式為virtual,則屬於派生類重寫基類的虛擬函式。

     作用效果:若重新定義了基類中的一個過載函式,則在派生類中,基類中該名字的函式(即其他所有過載版本)都被自動隱藏,包括同名的虛擬函式。

4. 重寫與過載的區別 (override) PK (overload)

(1) 方法的重寫是子類和父類之間的關係,是垂直關係;方法的過載是同一個類中方法之間的關係,是水平關係。

(2) 重寫要求引數列表相同;過載要求引數列表不同。

(3) 重寫關係中,呼叫那個方法體,是根據物件的型別(物件對應儲存空間型別)來決定;過載關係,是根據呼叫時的實參表與形參表來選擇方法體的。 

示例1:

class Base
{
   private:
      virtual voiddisplay() { cout<<"Base display()"<<endl; }
      voidsay(){  cout<<"Base say()"<<endl;  }
   public:
      void exec(){display(); say(); }
      voidf1(string a)  { cout<<"Base f1(string)"<<endl; }
      void f1(inta) { cout<<"Base f1(int)"<<endl; }   //overload
};
class DeriveA:public Base
{
   public:
      voiddisplay() { cout<<"DeriveA display()"<<endl;}   //override
      void f1(inta,int b) { cout<<"DeriveA f1(int,int)"<<endl;}   //redefining
      void say() {cout<<"DeriveA say()"<<endl; }   //redefining
};
class DeriveB:public Base
{
   public:
      void f1(inta) { cout<<"DeriveB f1(int)"<<endl; }  //redefining
};
  int main()
{
   DeriveA a;
   Base *b=&a;
   b->exec();       //display():version of DeriveAcall(polymorphism)                          
               //say():version of Base called(allways )
  a.exec();        //same result as laststatement
   a.say();
  //a.f1(1);         //error:no matchingfunction, hidden !!
   DeriveB c;
  c.f1(1);         //version of DeriveBcalled
}

示例2:

#include <iostream>
#include <windows.h>

using namespace std;
class Base
{
public:
	int a;

	Base()
	{
		cout << "Base() 無參建構函式!" << endl;
	}

	Base(int data)
	{
		a = data;
		cout << "Base(int data) 有參建構函式!" << endl;
	}

	Base(const Base &tmp)
	{
		a = tmp.a;
		cout << "Base 拷貝建構函式!!" << endl;
	}

	//基類中對函式名fuc1,進行了過載。其中最後一個過載函式為虛擬函式
	void fuc1()const
	{
		cout << "呼叫 void Base::fuc1()" << endl;
	}

	//overload
	int fuc1(int data) const
	{
		cout << "呼叫 Base fuc1(int data)" << endl;
		return 1;
	}

	//overload    虛擬函式
	virtual double fuc1(int dataA, int dataB)
	{
		cout << "呼叫 Base fuc1(int a,int b)" << endl;
		return dataA*dataB / 2.0;
	}

};

class  Derived : public Base
{
public:
	//先呼叫基類的建構函式,然後物件成員的建構函式,最後才是派生類的建構函式
	Base base;

	Derived()
	{
		cout << "Derived() 無參建構函式被呼叫!" << endl;
	}
	//對基類的函式名fuc1,進行了重定義。則會隱藏基類中的其他fuc1函式
	int  fuc1() const
	{
		cout << " 呼叫 Derived fuc1()函式" << endl;
		return 1;
	}

	//重寫基類的虛擬函式
	//redefine   override
	double fuc1(int dataA, int dataB)
	{
		cout << "呼叫 Derived fuc1(int dataA,int dataB)函式" << endl;
		return (dataA + dataB) / 2.0;
	}
};

int main()
{
	//-----test 1------------------------
	cout << "-------test 1------------" << endl;
	//Base class
	Base base;
	base.fuc1();
	base.fuc1(12);
	base.fuc1(10, 20);

	//derived class
	Derived derived;
	derived.fuc1();
	//derived.fuc1(2); //error C2661: 'fuc1' : no overloaded function takes 1 parameters
	derived.fuc1(10, 20);

	//--------test 2----------------------------------
	cout << "-------test 2------------" << endl;
	Base b = derived;
	b.fuc1();
	b.fuc1(10, 20);//呼叫的是基類的函式,不發生多型

	//--------test 3----------------------------------------
	cout << "-------test 3------------" << endl;
	Base &bR = derived;//引用
	b.fuc1();//fuc1()不是虛擬函式,呼叫基類的函式
	bR.fuc1(10, 20);//呼叫的是派生類的函式,發生多型

	//--------test 4--------------------------------------
	cout << "-------test 4------------" << endl;
	Base* pB = &derived;
	b.fuc1();
	pB->fuc1(10, 20);//呼叫的是派生類的函式,發生多型
	Sleep(50000);
	return 1;
}
         分析:test 1中進行了過載測試,根據傳遞引數的不一樣,呼叫不同的函式  (早繫結,與多型無關)。test 2中Test b = d;定義了一個基類物件,用派生類物件來進行初始化。這會呼叫基類的拷貝建構函式,生成基類的物件b,基類的拷貝建構函式初始化b的VPTR,指向b的VTABLE。因此所有的函式呼叫都只發生在基類,不會產生多型。 這是一個物件切片過程(參見《C++程式設計思想.第二版》),物件切片是當它拷貝到一個新的物件時,會去掉原來物件的一部分,而不是像使用指標或引用那樣簡單地改變地址的內容。test 3和test 4中,定義的基類指標和引用,故會發生多型。

        注意:在 C++ 中若基類中有一個函式名被過載,在子類中重定義該函式,則基類的所有版本將被隱藏——即子類只能用子類定義的,基類的不再可用。基類class Base中定義了名為fuc1的3個過載函式,其中最後一個是虛擬函式; 派生類class  Derived中對fuc1進行了重定義,所以會隱藏基類中名為fuc1的版本。其中派生類的double fuc1(int dataA,int dataB)屬於對虛擬函式的重寫。

5. 函式重入       一個函式在執行的過程中,還可以被另外一個地方呼叫執行。比如遞迴函式呼叫,多執行緒的多個執行緒同時執行一個函式。重入也就是同時執行一個函式的意思。而可重入表示的是一個函式是否可以同時執行而不出錯。如果一個函式執行過程會記錄狀態,比如使用全域性變數記錄,或者使用類成員變數記錄,然後根據狀態執行不同的程式碼,這樣就會導致第一個執行的和第二個同時執行的效果不一樣,因為第一個執行的已經改變了狀態,導致第二個執行的就是另外一個效果。這樣的表示不可重入。      所以,要讓函式重入,請保證不在函式中不可記錄狀態、將函式獨立成一個完整功能體,不與外部交涉。傳進去一個引數一定得到一個固定的結果,不管是多少個同時執行都如此。