1. 前言
關於指標函數和函數指標,特別是函數指標,相信很多C/C++ers跟我曾經一樣,對它抱有敬畏,認為它是很高深的東西,其實不然。要理解它花不了多少功夫,或許我一句話就能說清楚二者的區別,但是這樣也只是在腦子裡形成一個概念而已。大學時代,作為一名學生時,我可以一天看完毛概,考八九十分;但是我用了一個星期去看譚浩強的C++教材(儘管現在很多人鄙視這本教材),上機時卻仍無從下手,我可以侃侃而談,熟悉一切概念,但是就是編不出程式。這就是程式員的世界,凡事只有動手才能領悟真諦。不過這也應證了一句千古名句,也是我最喜歡的一句詩“紙上得來終覺淺,絕知此事要躬行”。
本文所有代碼編譯及運行環境:windows 7 professionnal, Visual Studio2010 professional.
2. 概述
按照行文的總-分-總的結構,這裡仍然先概括的介紹一下指標函數和函數指標的概念,然後再用程式來詳細的介紹二者。下面就是指標函數和函數指標的概念。
【指標函數】:返回指標的函數。重點是它是一個函數,只是傳回值由普通的值或對象變成了指標,也就是說這個函數返回的是一塊記憶體的地址。
【函數指標】:指向函數的指標。重點是它是一個指標,只是它指向的內容由普通的變數或對象變成了函數,也就是說它可以指向函數的入口地址。
3. 指標函數
在介紹指標函數之前,我們先來看一個普通的函數。
1 #include <iostream> 2 using namespace std; 3 4 class MyType{ 5 public: 6 MyType(int value):m_value(value){ 7 cout<<"Construct."<<endl; 8 } 9 ~MyType(){10 cout<<"Desconstruct."<<endl;11 }12 public:13 int m_value;14 };15 16 MyType getInstanceOfMyType(){17 MyType mt(10);18 cout<<&mt<<endl;19 return mt;20 }21 22 int main(){23 24 MyType mt = getInstanceOfMyType();25 cout<<&mt<<endl;26 cout<<mt.m_value<<endl;27 28 system("pause");29 return 0;30 }
塗色部分就是我們需要注意的地方,函數"getInstanceOfMyType()"內部建立了一個MyType的對象,接著輸出了該對象的地址,最後返回了該對象。main函數裡面,通過調用該函數,獲得了函數的傳回值,接著列印了返回對象的地址,再輸出獲得對象的m_value屬性的值。輸出結果如下:
Construct.0045F688Desconstruct.0045F78810請按任意鍵繼續. . .
可以看到,在"getInstanceOfMyType()"函數裡,對象建立之後又被銷毀了。從輸出可以看出,返回的對象地址與函數裡建立的對象地址是不一樣的,但是屬性m_value的值是一樣的,這說明通過該普通函數擷取的是函數內部建立對象的一個副本,這就是普通函數在返回對象時的處理。
在看到普通函數的處理之後,我們再來看一個指標函數的處理,下面是一段指標函數的代碼,注意,這段代碼與上一段很相似,要注意區分。
1 #include <iostream> 2 using namespace std; 3 4 class MyType{ 5 public: 6 MyType(int value):m_value(value){ 7 cout<<"Construct."<<endl; 8 } 9 ~MyType(){10 cout<<"Desconstruct."<<endl;11 }12 public:13 int m_value;14 };15 16 MyType *getInstanceOfMyType(){17 MyType *mt = new MyType(10);18 cout<<mt<<endl;19 return mt;20 }21 22 int main(){23 24 MyType *mt = getInstanceOfMyType();25 cout<<mt<<endl;26 cout<<mt->m_value<<endl;27 28 system("pause");29 return 0;30 }
上面代碼著色的部分需要我們注意,特別是函數"getInstanceOfMyType()",它在這裡已經是一個指標函數了,那麼,這段程式的輸出是什麼呢?如下:
Construct.00754AA800754AA810請按任意鍵繼續. . .
可以看出,在函數"getInstanceOfMyType()"中的對象一直沒有被調用解構函式,函數內和函數外的對象的地址是完全一樣的,當然,對象裡儲存的內容m_value的值也是一樣的。你可能會問,不是說函數調用完,就銷毀局部變數嗎?是的,它銷毀了,但是它只銷毀了"MyType *mt"這個指標,它指向的記憶體卻不會被銷毀。所以,在外面我們仍然可以繼續訪問這個對象。這種情況下,我們一般是需要在函數調用外面加上我們自己的delete操作的,上面的程式沒有添加這樣的操作,嚴格上來講是一個錯誤的程式。
使用指標函數時,直接返回函數內部對象的地址,這樣就無需重新製造對象的副本,對效率的提升有協助。但是需要注意的是,一定要記得在函數外部將函數內部申請的記憶體釋放掉,否則就有記憶體溢出的風險。
4. 函數指標
下面說道我們今天主要的話題了——函數指標。函數指標是一個很有用的技術,它使得我們可以通過指標就能執行某一個函數代碼。對於技術高超的人來說,它是一把【絕世好劍】,能夠解決很多問題。下面,我們就函數指標來探究一番。
首先,來看一段最簡單的函數指標的代碼,注意聲明和調用的方式。
1 #include <iostream> 2 using namespace std; 3 4 int printFunc(int value){ 5 cout<<"this is a print function. the value is:"<<value<<endl; 6 return 0; 7 } 8 9 int main(){10 11 int (*pFunction)(int x); // 這是一個函數指標變數12 pFunction = printFunc; // 這裡將函數入口地址給函數指標13 (*pFunction)(7); // 通過*運算子擷取了函數,再傳入參數7執行了函數14 15 system("pause");16 return 0;17 }
上述代碼著色部分就是函數指標的聲明-定義-執行的過程,可以看出來,我只要將函數入口地址給函數指標就可以執行函數了。這裡有個知識點,就是關於函數的調用方式,一般我們調用函數的方式是這樣的:函數名(參數列表),但是其實函數的調用方式也可以這樣來寫:(*函數名)(參數列表)。即可以如下來調用函數,只是很少這樣用:
1 (*printFunc)(8);
除此之外,我們還可以這樣寫:(&函數名)(參數列表)。即如下調用函數,這也幾乎沒人這樣用:
1 (&printFunc)(8);
對於函數指標,它有兩個前提:①.就是指向的函數傳回值要與聲明的函數指標一致。②.指向的函數的參數類型及個數要與聲明的函數指標一致。否則,是無法編譯通過的。
5. 函數指標類型
上面一節在使用函數指標的時候,直接聲明了一個函數指標。其實函數指標也可以藉助typedef聲明為一個類型,這樣我們就可以像定義int型變數一樣來定義一個函數指標了。定義函數指標類型代碼如下:
1 #include <iostream> 2 using namespace std; 3 4 int printFunc(int value){ 5 cout<<"this is a print function. the value is:"<<value<<endl; 6 return 0; 7 } 8 typedef int (*PFunction)(int x); // 函數指標類型,注意傳回值和參數列表 9 10 int main(){11 12 PFunction ptrFunc; // 定義函數指標變數13 ptrFunc = printFunc;14 (*ptrFunc)(1); // 第一種調用方式15 ptrFunc(2); // 第二種調用方式16 17 system("pause");18 return 0;19 }
6. 一個函數指標的妙用樣本
1 #include <iostream> 2 using namespace std; 3 4 typedef void (*PFunction)(int x); // 函數指標類型,注意傳回值和參數列表 5 6 void printA(int value){ 7 cout<<"A - "<<value<<endl; 8 } 9 10 void printB(int value){11 cout<<"B - "<<value<<endl;12 }13 14 void printC(int value){15 cout<<"C - "<<value<<endl;16 }17 18 int main(){19 int choice;20 PFunction ptrFunc[3] = {printA, printB, printC};21 cin>>choice;22 ptrFunc[choice](choice);23 24 system("pause");25 return 0;26 }
具體的這裡就不解說了,代碼很短,也很容易看懂。
7. 結語
本文就指標函數和函數指標做了一個簡單的入門講解,希望讀者在閱讀完本文以後,對指標函數和函數指標有一個深入的認識。當然,寫作本文的目的也是為了強化筆者的C++基礎功底。