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. 0045F688 Desconstruct. 0045F788 10 請按任意鍵繼續. . .
可以看到,在"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. 00754AA8 00754AA8 10 請按任意鍵繼續. . .
可以看出,在函數"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++基礎功底。