1. 程式人生 > >如何使用指向類的成員函式的指標(詳解!)

如何使用指向類的成員函式的指標(詳解!)

微笑吐舌頭我們首先複習一下"指向函式的指標"如何使用?

  void print()
  {
  }
  void (*pfun)(); //宣告一個指向函式的指標,函式的引數是 void,函式的返回值是 void
  pfun = print;   //賦值一個指向函式的指標
  (*pfun)();    //使用一個指向函式的指標

微笑吐舌頭比較簡單,不是嗎?為什麼*pfun需要用()擴起來呢?

微笑吐舌頭因為*的運算子優先順序比()低,如果不用()就成了*(pfun()).

微笑吐舌頭指向類的成員函式的指標不過多了一個類的限定而已!

  class A
  {
  void speak(char *, const char *); 
  };
  
  void main()
  {
  A a;
  void (A::*pmf)(char *, const char *);//指標的宣告
  pmf = &A::speak; //指標的賦值
  }

微笑吐舌頭一個指向類A 成員函式的指標宣告為:

微笑吐舌頭void (A::*pmf)(char *, const char *);

微笑吐舌頭宣告的解釋是:pmf是一個指向A成員函式的指標,返回無型別值,函式帶有二個參數,引數的型別分別是char *和const char *。除了在星號前增加A::,與宣告外部函式指標的方法一樣。一種更加高明的方法是使用型別定義:例如,下面的語句定義了PMA是一個指向類A成成員函式的指標,函式返回無型別值,函式引數型別為char *和const char *:

委屈快哭了typedef void(A::*PMA)(char *,const char *);

委屈快哭了PMA pmf= &A::strcat;//pmf是 PMF型別(類A成員指標)的變數

委屈快哭了下面請看關於指向類的成員函式的使用示例:

#include <iostream>
using namespace std;

class Person
{
public:
	/*這裡稍稍注意一下,我將speak()函式設定為普通的成員函式,而hello()函式設定為虛擬函式*/
	int value;
	void speak()	
	{
		cout << "I am a person!" << endl;
		printf ("%d\n", &Person::speak); /*在這裡驗證一下,輸出一下地址就知道了!*/
	}
	virtual void hello()
	{
		cout << "Person say \"Hello\"" << endl;
	}
	Person()
	{
		value = 1;
	}

};



class Baizhantang: public Person
{
public:
	void speak()
	{
		cout << "I am 白展堂!" << endl;
	}
	virtual void hello()
	{
		cout << "白展堂 say \"hello!\"" << endl;
	}
	Baizhantang()
	{
		value = 2;
	}
};

typedef void (Person::*p)();//定義指向Person類無引數無返回值的成員函式的指標
typedef void (Baizhantang::*q)();//定義指向Baizhantang類的無引數無返回值的指標

int main()
{
	Person pe;
	int i = 1;
	p ip;
	ip = &Person::speak;	//ip指向Person類speak函式
	(pe.*ip)();		//這個是正確的寫法!

	//--------------------------------------------
	//	result : I am a Person! 
	//			 XXXXXXXXXX(表示一段地址)
	//--------------------------------------------

	/*
	*下面是幾種錯誤的寫法,要注意!
	*		pe.*ip();
	* 		pe.(*ip)();
	*		(pe.(*ip))();
	*/

	Baizhantang bzt;
	
	q iq = (void (Baizhantang::*)())ip;	//強制轉換
	(bzt.*iq)();

	//--------------------------------------------
	//	result : I am a Person!
	//			 XXXXXXXXXX(表示一段地址)
	//--------------------------------------------

	/*	有人可能會問了:ip明明被強制轉換成了Baizhantang類的成員函式的指標,為什麼輸出結果還是:
	* I am a Person!在C++裡面,類的非虛擬函式都是採用靜態繫結,也就是說類的非虛擬函式在編譯前就已經
	*確定了函式地址!ip之前就是指向Person::speak函式的地址,強制轉換之後,只是指標型別變了,裡面
	*的值並沒有改變,所以呼叫的還是Person.speak函式,細心的傢伙會發現,輸出的地址都是一致的.
	*這裡要強調一下:對於類的非靜態成員函式,c++編譯器會給每個函式的引數新增上一個該類的指標this,這也
	*就是為什麼我們在非靜態類成員函式裡面可以使用this指標的原因,當然,這個過程你看不見!而對於靜態成員
	*函式,編譯器不會新增這樣一個this。
	*/
	
	iq = &Baizhantang::speak;	/*iq指向了Baizhantang類的speak函式*/
	ip = (void (Person::*)())iq;	/*ip接收強制轉換之後的iq指標*/
	(bzt.*ip)();

	//--------------------------------------------
	//	result : I am 白展堂!
	//--------------------------------------------

	(bzt.*iq)();//這裡我強調一下,使用了動態聯編,也就是說函式在執行是才確定函式地址!

	//--------------------------------------------
	//	result : I am 白展堂!
	//--------------------------------------------

	/*這一部分就沒有什麼好講的了,很明白了!由於speak函式是普通的成員函式,在編譯時就知道
	*到了Baizhantang::speak的地址,因此(bzt.*ip)()會輸出“I am 白展堂!”,即使iq被強制轉換
	*成(void (Person::*)())型別的ip,但是其值亦未改變,(bzt.*iq)()依然呼叫iq指向處的函式
	*即Baizhantang::speak.
	*/


	/*好了,上面講完了普通成員函式,我們現在來玩一點好玩的,現在來聊虛擬函式*/
	ip = &Person::hello;	/*讓ip指向Person::hello函式*/
	(pe.*ip)();

	//--------------------------------------------
	//	result : Person say "Hello"
	//--------------------------------------------

	(bzt.*ip)();

	//--------------------------------------------
	//	result : 白展堂 say "Hello"
	//--------------------------------------------

	/*咦,這就奇怪了,為何與上面的呼叫結果不類似?為什麼兩個呼叫結果不一致?夥伴們注意了:
	*speak函式是一個虛擬函式,前面說過虛擬函式並不是採用靜態繫結的,而是採用動態繫結,所謂動態
	*繫結,就是函式地址得等到執行的時候才確定,對於有虛擬函式的類,編譯器會給我們新增一個指標
	*vptr,指向一個虛擬函式表vptl,vptl裡面存放著虛擬函式的地址,子類繼承父類的時候,也會繼承這樣
	*一個指標,如果子類複寫了虛擬函式,那麼該表中該虛擬函式地址將會由父類的虛擬函式地址替換成子類虛
	*函式地址,編譯器會把(pe.*ip)()轉化成為(pe.vptr[1])(pe),加上動態繫結,結果會輸出:
	*       Person say "Hello"   
	*(bzt.*ip)()會被轉換成(bzt.vptr[1])(pe),自然會輸出:
	*		白展堂 say "Hello"
	*ps:這裡我沒法講得更詳細,因為解釋起來肯定是很長很長的,感興趣的話,我推薦兩本書你去看一看:
	*	第一本是侯捷老師的<深入淺出MFC>,裡面關於c++的虛擬函式特性講的比較清楚;
	*	第二本是侯捷老師翻譯的<深度探索C++物件模型>,一聽名字就知道,講這個就更詳細了;
	*當然,不感興趣的同學這段解釋可以省略,對與使用沒有影響!
	*/

	iq = (void (Baizhantang::*)())ip;
	(bzt.*iq)();

	//--------------------------------------------
	//	result : 白展堂 say "Hello"
	//--------------------------------------------
	
	system("pause");
	return 0;
}