天天看點

(轉)徹底搞定C指針-函數名與函數指針

 (轉自)http://blog.pfan.cn/whyhappy/6030.html

 函數名與函數指針

一 通常的函數調用

    一個通常的函數調用的例子:

//自行包含頭檔案

void MyFun(int x);    //此處的申明也可寫成:void MyFun( int );

int main(int argc, char* argv[])

{

   MyFun(10);     //這裡是調用MyFun(10);函數

      return 0;

}

void MyFun(int x)  //這裡定義一個MyFun函數

{

   printf(“%d/n”,x);

}

    這個MyFun函數是一個無傳回值的函數,它并不完成什麼事情。這種調用函數的格式你應該是很熟悉的吧!看主函數中調用MyFun函數的書寫格式:

MyFun(10);

    我們一開始隻是從功能上或者說從數學意義上了解MyFun這個函數,知道MyFun函數名代表的是一個功能(或是說一段代碼)。

    直到——

    學習到函數指針概念時。我才不得不在思考:函數名到底又是什麼東西呢?

    (不要以為這是沒有什麼意義的事噢!呵呵,繼續往下看你就知道了。)

二 函數指針變量的申明

    就象某一資料變量的記憶體位址可以存儲在相應的指針變量中一樣,函數的首位址也以存儲在某個函數指針變量裡的。這樣,我就可以通過這個函數指針變量來調用所指向的函數了。

    在C系列語言中,任何一個變量,總是要先申明,之後才能使用的。那麼,函數指針變量也應該要先申明吧?那又是如何來申明呢?以上面的例子為例,我來申明一個可以指向MyFun函數的函數指針變量FunP。下面就是申明FunP變量的方法:

void (*FunP)(int) ;   //也可寫成void (*FunP)(int x);

    你看,整個函數指針變量的申明格式如同函數MyFun的申明處一樣,隻不過——我們把MyFun改成(*FunP)而已,這樣就有了一個能指向MyFun函數的指針FunP了。(當然,這個FunP指針變量也可以指向所有其它具有相同參數及傳回值的函數了。)

三 通過函數指針變量調用函數

    有了FunP指針變量後,我們就可以對它指派指向MyFun,然後通過FunP來調用MyFun函數了。看我如何通過FunP指針變量來調用MyFun函數的:

//自行包含頭檔案

void MyFun(int x);    //這個申明也可寫成:void MyFun( int );

void (*FunP)(int );   //也可申明成void(*FunP)(int x),但習慣上一般不這樣。

int main(int argc, char* argv[])

{

   MyFun(10);     //這是直接調用MyFun函數

   FunP=&MyFun;  //将MyFun函數的位址賦給FunP變量

   (*FunP)(20);    //這是通過函數指針變量FunP來調用MyFun函數的。

}

void MyFun(int x)  //這裡定義一個MyFun函數

{

   printf(“%d/n”,x);

}

    請看黑體字部分的代碼及注釋。

    運作看看。嗯,不錯,程式運作得很好。

    哦,我的感覺是:MyFun與FunP的類型關系類似于int 與int *的關系。函數MyFun好像是一個如int的變量(或常量),而FunP則像一個如int *一樣的指針變量。

int i,*pi;

pi=&i;    //與FunP=&MyFun比較。

    (你的感覺呢?)

    呵呵,其實不然——

四 調用函數的其它書寫格式

函數指針也可如下使用,來完成同樣的事情:

//自行包含頭檔案

void MyFun(int x);    

void (*FunP)(int );    //申明一個用以指向同樣參數,傳回值函數的指針變量。

int main(int argc, char* argv[])

{

   MyFun(10);     //這裡是調用MyFun(10);函數

   FunP=MyFun;  //将MyFun函數的位址賦給FunP變量

   FunP(20);    //這是通過函數指針變量來調用MyFun函數的。

      return 0;

}

void MyFun(int x)  //這裡定義一個MyFun函數

{

   printf(“%d/n”,x);

}

我改了黑體字部分(請自行與之前的代碼比較一下)。

運作試試,啊!一樣地成功。

咦?

FunP=MyFun;

可以這樣将MyFun值同指派給FunP,難道MyFun與FunP是同一資料類型(即如同的int 與int的關系),而不是如同int 與int*的關系了?(有沒有一點點的糊塗了?)

看來與之前的代碼有點沖突了,是吧!是以我說嘛!

請容許我暫不給你解釋,繼續看以下幾種情況(這些可都是可以正确運作的代碼喲!):

代碼之三:

int main(int argc, char* argv[])

{

   MyFun(10);     //這裡是調用MyFun(10);函數

   FunP=&MyFun;  //将MyFun函數的位址賦給FunP變量

   FunP(20);    //這是通過函數指針變量來調用MyFun函數的。

      return 0;

}

代碼之四:

int main(int argc, char* argv[])

{

   MyFun(10);     //這裡是調用MyFun(10);函數

   FunP=MyFun;  //将MyFun函數的位址賦給FunP變量

   (*FunP)(20);    //這是通過函數指針變量來調用MyFun函數的。

      return 0;

}

真的是可以這樣的噢!

(哇!真是要暈倒了!)

還有呐!看——

int main(int argc, char* argv[])

{

   (*MyFun)(10);     //看,函數名MyFun也可以有這樣的調用格式

      return 0;

}

你也許第一次見到吧:函數名調用也可以是這樣寫的啊!(隻不過我們平常沒有這樣書寫罷了。)

那麼,這些又說明了什麼呢?

呵呵!假使我是“福爾摩斯”,依據以往的知識和經驗來推理本篇的“新發現”,必定會由此分析并推斷出以下的結論:

1. 其實,MyFun的函數名與FunP函數指針都是一樣的,即都是函數指針。MyFun函數名是一個函數指針常量,而FunP是一個函數數指針變量,這是它們的關系。

2. 但函數名調用如果都得如(*MyFun)(10);這樣,那書寫與讀起來都是不友善和不習慣的。是以C語言的設計者們才會設計成又可允許MyFun(10);這種形式地調用(這樣友善多了并與數學中的函數形式一樣,不是嗎?)。

3. 為統一起見,FunP函數指針變量也可以FunP(10)的形式來調用。

4. 指派時,即可FunP=&MyFun形式,也可FunP=MyFun。

上述代碼的寫法,随便你愛怎麼着!

請這樣了解吧!這可是有助于你對函數指針的應用喽!

最後——

補充說明一點:在函數的申明處:

void MyFun(int );    //不能寫成void (*MyFun)(int )。

void (*FunP)(int );   //不能寫成void FunP(int )。

(請看注釋)這一點是要注意的。

五 定義某一函數的指針類型:

就像自定義資料類型一樣,我們也可以先定義一個函數指針類型,然後再用這個類型來申明函數指針變量。

我先給你一個自定義資料類型的例子。

typedef int* PINT;    //為int* 類型定義了一個PINT的别名

int main()

{

  int x;

  PINT px=&x;   //與int * px=&x;是等價的。PINT類型其實就是int * 類型

  *px=10;       //px就是int*類型的變量  

  return 0;

}

根據注釋,應該不難看懂吧!(雖然你可能很少這樣定義使用,但以後學習Win32程式設計時會經常見到的。)

下面我們來看一下函數指針類型的定義及使用:(請與上對照!)

//自行包含頭檔案

void MyFun(int x);    //此處的申明也可寫成:void MyFun( int );

typedef void (*FunType)(int );   //這樣隻是定義一個函數指針類型

FunType FunP;              //然後用FunType類型來申明全局FunP變量

int main(int argc, char* argv[])

{

//FunType FunP;    //函數指針變量當然也是可以是局部的 ,那就請在這裡申明了。

   MyFun(10);     

   FunP=&MyFun;  

   (*FunP)(20);    

      return 0;

}

void MyFun(int x)  

{

   printf(“%d/n”,x);

}

看黑體部分:

首先,在void (*FunType)(int ); 前加了一個typedef 。這樣隻是定義一個名為FunType函數指針類型,而不是一個FunType變量。

然後,FunType FunP;  這句就如PINT px;一樣地申明一個FunP變量。

其它相同。整個程式完成了相同的事。

這樣做法的好處是:

有了FunType類型後,我們就可以同樣地、很友善地用FunType類型來申明多個同類型的函數指針變量了。如下:

FunType FunP2;

FunType FunP3;

//……

六 函數指針作為某個函數的參數

既然函數指針變量是一個變量,當然也可以作為某個函數的參數來使用的。是以,你還應知道函數指針是如何作為某個函數的參數來傳遞使用的。

給你一個執行個體:

要求:我要設計一個CallMyFun函數,這個函數可以通過參數中的函數指針值不同來分别調用MyFun1、MyFun2、MyFun3這三個函數(注:這三個函數的定義格式應相同)。

實作:代碼如下:

//自行包含頭檔案

void MyFun1(int x);  

void MyFun2(int x);  

void MyFun3(int x);  

typedef void (*FunType)(int ); //②. 定義一個函數指針類型FunType,與①函數類型一至

void CallMyFun(FunType fp,int x);

int main(int argc, char* argv[])

{

   CallMyFun(MyFun1,10);   //⑤. 通過CallMyFun函數分别調用三個不同的函數

   CallMyFun(MyFun2,20);   

   CallMyFun(MyFun3,30);   

}

void CallMyFun(FunType fp,int x) //③. 參數fp的類型是FunType。

{

  fp(x);//④. 通過fp的指針執行傳遞進來的函數,注意fp所指的函數是有一個參數的

}

void MyFun1(int x) // ①. 這是個有一個參數的函數,以下兩個函數也相同

{

   printf(“函數MyFun1中輸出:%d/n”,x);

}

void MyFun2(int x)  

{

   printf(“函數MyFun2中輸出:%d/n”,x);

}

void MyFun3(int x)  

{

   printf(“函數MyFun3中輸出:%d/n”,x);

}

輸出結果:略

分析:(看我寫的注釋。你可按我注釋的①②③④⑤順序自行分析。)

附錄

c#中的代理(差別)

任何編寫過圖形使用者界面(GUI)軟體的開發人員都熟悉事件處理程式設計,當使用者與GUI控制進行互動時(例如點選表格上的按鈕),作為上述事件的反應,就會執行一個或多個方法。沒有使用者的參與,事件也可能執行。事件處理程式是對象的方法,是根據應用程式中發生的事件而執行的。為了了解.Net架構下的事件處理模式,我們需要了解代理的概念。

C#中的代理

C#中的代理允許我們将一個類中的方法傳遞給其他類的對象。我們能夠将類A中的方法m封裝為一個代理,傳遞給類B,類B能夠調用類A中的方法m,靜态和執行個體方法都可以傳送。C++軟體開發人員應該對這一概念非常熟悉,在C++中,開發人員能夠以參數的形式使用函數指針将函數傳遞給同理個類或其他類中的方法。代理的概念是在Visulal J++中引入的,然後又被帶到了C#中。在.Net架構中C#的代理是以從System.Delegate中繼承的類的形式實作的。使用代理需要4個步驟:

1、定義一個輸入參數與要進行封裝的方法完全相同的代理對象。

2、定義所有輸入參數與在第1步中定義的代理對象相同的方法。

3、建立代理對象,并與希望封裝的方法進行連接配接。

4、通過代理對象調用封裝的方法。

附錄B

c++中變量未初始化,對于全局變量預設的初始化為對應的“0”。如果局部變量是個随機值 如果是指針變量,指針不初始化為NULL是個不好的做法,

1,預設的随機值不是你能夠通路的,當然報“跨省”出錯了 。你初始化為NULL的好處是在,可以用if(NULL == p)防止錯誤,如果你不NULL,很有可能就是非法通路

2,指針沒有初始化,那麼,有可能可以正常執行,有可能不可以。pi2指向的位址是随機的。 但如果正常執行了,那麼就會有把記憶體中的某一塊4個位元組的内容,修改成了1024。導緻程式運作出現非常奇怪的錯誤。“記憶體中的某一塊4個位元組”就是未初始化的某個指針剛好偶然指向的那的地方,如果這是個 int * 指針,那麼編譯器認為那個地方放了一個 int ,這個 int 占 4 位元組。 如果這個位址正好又是程式可以通路的,那麼操作它是可以的,但是鬼知道那個地方本來是做什麼用的…… 如果這 4 個位元組本來是程式的其他部分用的,你就這麼把它的内容改掉了……然後不知道什麼時候真正用到這個位址上資料的那部分程式開工了

上面1和2 的說話不一定正确,隻是在網上一些人的一種說法

附錄C

引用(reference)是c++的初學者比較容易迷惑的概念。下面我們比較詳細地讨論引用。

一、引用的概念

引用引入了對象的一個同義詞。定義引用的表示方法與定義指針相似,隻是用&代替了*。

例如: Point pt1(10,10);

Point &pt2=pt1; 定義了pt2為pt1的引用。通過這樣的定義,pt1和pt2表示同一對象。

需要特别強調的是引用并不産生對象的副本,僅僅是對象的同義詞。是以,當下面的語句執行後:

pt1.offset(2,2);

pt1和pt2都具有(12,12)的值。

引用必須在定義時馬上被初始化,因為它必須是某個東西的同義詞。你不能先定義一個引用後才

初始化它。例如下面語句是非法的:

Point &pt3;

pt3=pt1;

那麼既然引用隻是某個東西的同義詞,它有什麼用途呢?

下面讨論引用的兩個主要用途:作為函數參數以及從函數中傳回左值。

二、引用參數

1、傳遞可變參數

傳統的c中,函數在調用時參數是通過值來傳遞的,這就是說函數的參數不具備傳回值的能力。

是以在傳統的c中,如果需要函數的參數具有傳回值的能力,往往是通過指針來實作的。比如,實作

兩整數變量值交換的c程式如下:

void swapint(int *a,int *b)

{

int temp;

temp=*a;

a=*b;

*b=temp;

}

使用引用機制後,以上程式的c++版本為:

void swapint(int &a,int &b)

{

int temp;

temp=a;

a=b;

b=temp;

}

調用該函數的c++方法為:swapint(x,y); c++自動把x,y的位址作為參數傳遞給swapint函數。

2、給函數傳遞大型對象

當大型對象被傳遞給函數時,使用引用參數可使參數傳遞效率得到提高,因為引用并不産生對象的

副本,也就是參數傳遞時,對象無須複制。下面的例子定義了一個有限整數集合的類:

const maxCard=100;

Class Set

{

int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素個數的最大值。

int card; // 集合中元素的個數。

public:

Set () {card=0;} //構造函數

friend Set operator * (Set ,Set ) ; //重載運算符号*,用于計算集合的交集 用對象作為傳值參數

// friend Set operator * (Set & ,Set & ) 重載運算符号*,用于計算集合的交集 用對象的引用作為傳值參數

...

}

先考慮集合交集的實作

Set operator *( Set Set1,Set Set2)

{

Set res;

for(int i=0;i<Set1.card;++i)

for(int j=0;j>Set2.card;++j)

if(Set1.elems[i]==Set2.elems[j])

{

res.elems[res.card++]=Set1.elems[i];

break;

}

return res;

}

由于重載運算符不能對指針單獨操作,我們必須把運算數聲明為 Set 類型而不是 Set * 。

每次使用*做交集運算時,整個集合都被複制,這樣效率很低。我們可以用引用來避免這種情況。

Set operator *( Set &Set1,Set &Set2)

{ Set res;

for(int i=0;i<Set1.card;++i)

for(int j=0;j>Set2.card;++j)

if(Set1.elems[i]==Set2.elems[j])

{

res.elems[res.card++]=Set1.elems[i];

break;

}

return res;

}

三、引用傳回值

如果一個函數傳回了引用,那麼該函數的調用也可以被指派。這裡有一函數,它擁有兩個引用參數并傳回一個雙精度數的引用:

double &max(double &d1,double &d2)

{

return d1>d2?d1:d2;

}

由于max()函數傳回一個對雙精度數的引用,那麼我們就可以用max() 來對其中較大的雙精度數加1:

max(x,y)+=1.0;

傳回引用值要注意,不能傳回臨時、局部變量的引用

繼續閱讀