1. 程式人生 > >【C/C++開發】函式指標與回撥函式

【C/C++開發】函式指標與回撥函式

C++很多類庫都喜歡用回撥函式,MFC中的定時器,訊息機制,hook機制等待,包括現在在研究的cocos2d-x中也有很多的回撥函式。


1.回撥函式

什麼是回撥函式呢?回撥函式其實就是一個通過函式指標呼叫的函式!假如你把A函式的指標當作引數傳給B函式,然後在B函式中通過A函式傳進來的這個指標呼叫A函式,那麼這就是回撥機制。A函式就是回撥函式,而通常情況下,A函式是系統在符合你設定條件的情況下會自動執行,比如Windows下的訊息觸發等等。那麼呼叫者和被呼叫者的關係就被拉開了,就像是中斷處理函式那樣。


2.函式指標

函式指標是一個指標,只是這個指標它不像普通的指標指向是是一個變數,此時它指向的是一個函式,也就是它儲存的是一個函式的地址,如果我們願意的話,可以改變這個它的值,讓他由指向funA轉變為指向funB,那麼這個函式指標的作用就改變了。


3.函式指標的使用


3.1函式指標宣告

typedef 返回型別(*函式指標型別名)(函參列表);


3.2示例

  1. typedef void(*Fun)(int,int); //定義函式指標型別
  2. void min(int a,int b);
  3. void max(int a,int b);
  4. void min(int a,int b)
  5. {
  6. int minvalue=a<b?a:b;
  7. std:: cout<< "min value is "<<minvalue<< "\n";
  8. }
  9. void max(int a,int b)
  10. {
  11. int maxvalue=a>b?a:b;
  12. std:: cout<< "Max value is "<<maxvalue<< "\n";
  13. }
  14. int _tmain( int argc, _TCHAR* argv[])
  15. {
  16. Fun pFun= NULL; //定義函式指標變數pFun
  17. //pFun=min;//兩種賦值方式都支援
  18. pFun=&min;
  19. pFun( 1, 2); //這裡獲得最小值
  20. //pFun=max;
  21. pFun=&max;
  22. pFun( 1, 2); //這裡獲得最大值
  23. return 0;
  24. }
我想這麼寫應該是很一目瞭然了。


4.回撥函式的使用

回撥函式的使用其實和上面函式指標示例是很一致的,只是上面的例項中pFun是我們自己呼叫的。現在我們在MFC中讓系統呼叫一下吧。


先看一下SetTimer函式宣告:

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
其中lpfnTimer是這麼解釋的:Specifies the address of the application-supplied TimerProc callback function that processes the WM_TIMER messages. If this parameter is NULL, the WM_TIMER messages are placed in the application's message queue and handled by the CWnd object.

也就是或如果為NULL的話,系統自動觸發WM_Timer訊息,然後呼叫OnTimer函式。


我們呼叫自己的自定義回撥函式。

  1. CALLBACK VOID callback_fun(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
  2. {
  3. TRACE( "CallBack\n"); //debug下執行控制檯輸出CallBack
  4. }
  5. void CTestCallBackDlg::OnBnClickedButton1()
  6. {
  7. // TODO: 在此新增控制元件通知處理程式程式碼
  8. SetTimer( 1, 1000,(TIMERPROC)callback_fun); //每一秒回撥一次
  9. }

另外,需要注意的是回撥函式必須是全域性函式或者靜態成員函式,因為普通的成員函式會隱含著一個傳遞函式作為引數,也就是this指標。因此如果使用普通成員函式作為回撥函式的話會導致函式引數個數不匹配,因此編譯失敗。這也是執行緒函式是多為靜態函式的原因。

我們還注意到回撥函式用CALLBACK修飾,我們可以在windef.h中發現:

#define CALLBACK    __stdcall

CALLBACK其實就是__stdcall,還記得上篇講過的函式呼叫約定嗎?


如果試圖直接使用C++的成員函式作為回撥函式將發生錯誤,甚至編譯就不能通過。
其錯誤是普通的C++成員函式都隱含了一個傳
遞函式作為引數,亦即“this”指標,C++通過傳遞this指標給其成員函式從而實現成員函式可以訪問C++的資料成員。這也可以理解為什麼C++類的多個例項可以共享成員函式卻-有不同的資料成員。由於this指標的作用,使得將一個CALL-BACK型的成員函式作為回撥函式安裝時就會因為隱含的this指標使得函式引數個數不匹配,從而導致回撥函式安裝失敗。要解決這一問題的關鍵就是不讓this指標起作用,通過採用以下兩種典型技術可以解決在C++中使用回撥函式所遇到的問題。這種方法具有通用性,適合於任何C++。   
   
  1).   不使用成員函式,為了訪問類的成員變數,可以使用友元操作符(friend),在C++中將該函式說明為類的友元即可。       
  2).   使用靜態成員函式,靜態成員函式不使用this指標作為隱含引數,這樣就可以作為回撥函數了。靜態成員函式具有兩大特點:其一,可以
在沒有類例項的情況下使用;其二,只能訪問靜態成員變數和靜態成員函式,不能訪問非靜態成員變數和非靜態成員函式。由於在C++中使用類成員函式作為回撥函式的目的就是為了訪問所有的成員變數和成員函式,如果做不到這一點將不具有實際意義。解決的辦法也很簡單,就是使用一個靜態類指標作為類成員,通過在類建立時初始化該靜態指標,如pThis=this,然後在回撥函式中通過該靜態指標就可以訪問所有成員變數和成員函數了。

這種處理辦法適用於只有一個類例項的情況,因為多個類例項將共享靜態類成員和靜態成員函式,這就導致靜態指標指向最後建立的類例項。為了避免這種情況,可以使用回撥函式的一個引數來傳遞this指標,從而實現資料成員共享。這種方法稍稍麻煩,這裡就不再贅述。關於靜態方法訪問非靜態變數和函式的方式請見http://www.cnblogs.com/this-543273659/archive/2011/08/29/2157966.html

首先明白什麼是回撥函式:比如說被調函式void callbackf(int n){}要想作為回撥函式的話,callbackf必須作為主調函式的形參出現,如void f(void (*p(int)),int n)形式才行!

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

如果我要在static中呼叫類中的成員,怎麼做?
我得定義一個全域性的 指向該類的指標,然後在類的初始化函式裡面將 類的this 指標賦值給該全域性指標。
然後在static函式中通過該全域性指標呼叫類的其它成員。

例子1:

複製程式碼
#include "stdafx.h"
#include <iostream>
#include <assert.h>
using namespace std;

class Test
{
public:

    friend void callBackFun(void){ cout << "CallBack Function!";} //友元函式作為回撥函式 friend方式實現,											  //因為callBackFun預設有一個const Test* 的指標
};

typedef void (*FPtr)(void);

void Fun(FPtr ptr)
{
    ptr();
}



int main(void)
{
    Fun(callBackFun); 

    return 0;
}

  

例2:

#include<iostream> using namespace std; class A { public:    static void callback()  //類的成員函式作為回撥函式 static方式實現  {   cout<<"回撥函式開始執行了!"<<endl;  } }; void f(void (*p)())  {   p();

 } int main() {  void (*p)();  p=A::callback;  f(p);  return 0; }

還可以把f()函式設為類的成員函式:

#include<iostream> using namespace std; class A { public:    static void callback()  //類的成員函式作為回撥函式 static方式實現  {   cout<<"回撥函式開始執行了!"<<endl;  }  void f(void (*p)())  {   p();

 } };

int main() {  A a;  void (*p)();  p=A::callback;  a.f(p);  return 0; }

///什麼是回撥函式

  
   
   
    
回撥函式就是那些自己寫的,但是不是自己來調,而是給別人來呼叫的函式。
訊息響應函式就可以看成是回撥函式,因為是讓系統在合適的時候去呼叫。這不過訊息響應函式就是為了處理訊息的,所以就拿出來單做一類了。其實本質上就是回撥函式。
但是回撥函式不是隻有訊息響應函式一種,比如在核心程式設計中,驅動程式就要提供一些回撥函式,當一個裝置的資料讀寫完成後,讓系統呼叫這些回撥函式來執行一些後續工作。回撥函式賦予程式設計師這樣一種能力,讓自己編寫的程式碼能夠跳出正常的程式控制流,適應具體的執行環境在正確的時間執行。
////////////////////////////////////////////
普通的函式是:咱們的函式呼叫系統的函式,
把你寫的程式和系統已經封裝好的函式看成兩個部分
你的程式使用系統的函式 那叫 呼叫
系統函式使用你的程式函式 就叫回調
一般多用於系統函式與你的函式要進行非同步處理
比如按鍵事件,其實是個訊息
你的函式比按鍵事件更早存在
所以你要將這個函式做為回撥函式提交給系統,
然後系統在接收到按鍵事件後,再呼叫你的函式
/////////////////////////////////////////////
比如:void fun(){printf();}
而回調函式是:系統呼叫你的函式。
win32 程式設計的WndProc,java的事件,c#的delegate都是這種思想。可以說沒有壞處,回撥使得系統更加靈活。
函式指標做為函式的引數,傳遞給一個被呼叫函式,
被呼叫函式就可以通過這個指標呼叫外部的函式,這就形成了回撥

一般的程式中回撥函式作用不是非常明顯,可以不使用這種形式

最主要的用途就是當函式不處在同一個檔案當中,比如動態庫,要呼叫
其他程式中的函式就只有採用回撥的形式

#include "stdio.h"
#include "conio.h"

int add(int a, int b);
int libfun(int (*pDis)(int a, int b));

int main(void)
{
int (*pfun)(int a, int b);

pfun = add;
libfun(pfun);//呼叫int libfun(int (*pDis)(int a, int b))


}

int add(int a, int b)
{
return a + b;

}

int libfun(int (*pDis)(int a, int b))//回撥add()函式
{
int a, b;
a = 1;
b = 2;
printf("%d", pDis(a, b));

}

現在這幾個函式是在同一個檔案當中

假如 
int libfun(int (*pDis)(int a, int b))
是一個庫中的函式,就只有使用回調了,通過函式指標引數將外部函式地址傳入
來實現呼叫

函式 add 的程式碼作了修改,也不必改動庫的程式碼,就可以正常實現呼叫
便於程式的維護和升級

摘要:回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應。


按照上面的說法,實現一個C Language的回撥函式如下,

  1. #include <stdio.h>
  2. //回撥函式
  3. int ADD(int (*callback)(int,int), int a, int b){
  4. return (*callback)(a,b); //此處回撥add函式...
  5. }
  6. //普通函式
  7. int add(int a, int b){
  8. return a + b;
  9. }
  10. int main(void){
  11. printf( "%d\n",add( 1, 2));
  12. printf( "%d\n",ADD(add, 1, 2));
  13. return 0;
  14. }

從上面的定義及其實現可以看出,回撥函式必須有函式指標的存在,而這裡函式指標一般可以先typedef一下,然後再使用,

一般格式: 返回值 (*指標名) (引數列表)

  1. #include <stdio.h>
  2. //返回值(*指標名)(引數列表)
  3. typedef int (*callback)(int,int);
  4. //回撥函式
  5. int ADD(callback p, int a, int b){
  6. return (*p)(a,b); //此處回撥add函式...
  7. }
  8. //普通函式
  9. int add(int a, int b){
  10. return a + b;
  11. }
  12. int main(void){
  13. printf( "%d\n",add( 1, 2));
  14. printf( "%d\n",ADD(add, 1, 2));
  15. return 0;
  16. }

可是,根據上面的例子,回撥函式搞得這麼麻煩,貌似並沒有什麼大作用.....糾結!

別糾結,來看一下庫函式中的sort排序是怎麼弄的。algorithm它提供了某些排序演算法的實現(如氣泡排序、快速排序、shell排序、shake排序等等),為了能讓庫更加通用,不想在函式中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,能讓庫可用於多種資料型別(int、float、string),此時,該怎麼辦呢?可以使用函式指標,並進行回撥,如下:

  1. #include <stdio.h>
  2. #include <algorithm>
  3. bool cmp(int a, int b){
  4. return a > b;
  5. }
  6. int main(void){
  7. int a[ 8] = { 5, 43, 1, 7, 8, 13, 0, 74};
  8. std::sort(a,a+ 10,cmp); //callback
  9. 相關推薦

    C/C++開發函式指標函式

    C++很多類庫都喜歡用回撥函式,MFC中的定時器,訊息機制,hook機制等待,包括現在在研究的cocos2d-x中也有很多的回撥函式。 1.回撥函式 什麼是回撥函式呢?回撥函式其實就是一個通過函式指標呼叫的函式!假如你把A函式的指標當作引數傳給B函式,然後在B函式中通過A函式傳進來的這個指標

    深入淺出剖析C語言函式指標函式(一)

    關於靜態庫和動態庫的使用和製作方法。http://blog.csdn.net/morixinguan/article/details/52451612今天我們要搞明白的一個概念叫回調函式。什麼是回撥函式?百度的權威解釋如下:回撥函式就是一個通過函式指標呼叫的函式。如果你把函式

    C++基礎之八函式指標函式

    C++很多類庫都喜歡用回撥函式,MFC中的定時器,訊息機制,hook機制等待,包括現在在研究的cocos2d-x中也有很多的回撥函式。1.回撥函式什麼是回撥函式呢?回撥函式其實就是一個通過函式指標呼叫的函式!假如你把A函式的指標當作引數傳給B函式,然後在B函式中通過A函式傳進

    不在混淆的C指標函式函式指標函式

    一、指標函式 函式的返回值是指標型別。 int* fun(int a,int b); 指標函式使用: 返回字串 這裡要注意,"1234567890abc"是字串常量,*p指向的字串地址,返回的是這個地址,因為字串常量不會因為函式消亡而釋放,所有主函式依然可以訪問到地址的內容。 #include <

    C語言實現動態陣列 C語言函式指標函式

    實現任意資料型別的動態陣列的初始化,插入,刪除(按值刪除;按位置刪除),銷燬功能。、 動態陣列結構體   實現動態陣列結構體的三個要素:(1)陣列首地址;(2)陣列的大小;(3)當前陣列元素的個數。 1 //動態陣列結構體 2 struct DynamicArray{ 3 void **a

    Linux C 函式指標應用---函式

        (這裡引用了知乎上一些知友的回答,感覺不錯,有助於理解,這裡引用作為借鑑,如有冒犯,煩請告知)     我們先來回顧一下函式指標,函式指標是專門用來存放函式地址的指標,函式地址是一個函式的入口地址,函式名代表了函式的入口地址。當一個函式指標指向了一個函式,就可以通過

    函式指標函式 GObject 閉包 GObject 子類物件的析構過程 GObject 的訊號機制——概覽

    手冊所述,GObject 訊號(Gignal)主要用於特定事件與響應者之間的連線,它與作業系統級中的訊號沒有什麼關係。例如,當我向一個檔案中寫入資料的時候,我期望能夠有一個或多個函式響應這個“向檔案寫入資料”的事件,這一期望便可基於 GObject 訊號予以實現。 為了更好的理解 GObje

    函式指標作為某個函式的引數及定義函式指標函式

    轉載於:http://blog.csdn.net/vlily/article/details/7244682 轉載於:http://blog.csdn.net/shengnan_wu/article/details/8116935 轉載於:http://blog.csdn

    C++解析(20):智慧指標型別轉換函式

    0.目錄 1.智慧指標 2.轉換建構函式 3.型別轉換函式 4.小結 1.智慧指標 記憶體洩漏(臭名昭著的Bug): 動態申請堆空間,用完後不歸還 C++語言中沒有垃圾回收機制 指標無法控制所指堆空間的生命週期 我們需要什麼: 需要一個特殊的指標 指標生命週期結束

    轉載鉤子函式函式

    在訊息處理機制中必不可少的一組CP,即回撥和鉤子。 鉤子的概念源於Windows的訊息處理機制,通過設定鉤子,應用程式可以對所有的訊息事件進行攔截,然後執行鉤子函式,對訊息進行想要的處理方式。 接下來是一段js程式碼,主要用於給btn設定點選的鉤子函式。 let btn = document.getEl

    C#委託,事件函式

    using System;using System.Collections.Generic;using System.Text;namespace TestApp{    ///<summary>/// 委託    ///</summary>///<param name="s1

    詳解C#委託,事件函式

    .Net程式設計中最經常用的元素,事件必然是其中之一。無論在ASP.NET還是WINFrom開發中,窗體載入(Load),繪製(Paint),初始化(Init)等等。“protected void Page_Load(object sender, EventArgs e)”這

    C#:使用海康SDK解碼函式DecCallbackFUN()

    簡述   使用海康攝像頭採集影象時,需要在影象上新增圖示、文字等額外資料。可選擇使用海康SDK繪圖回撥函式疊加字元、影象等(請參考上一篇文章);也可使用海康SDK的解碼回撥函式,對視訊流資料進行解碼後處理。該方法流程為:呼叫視訊預覽函式NET_DVR_RealPlay_V40()時將第三個引數設定為實時資料

    指標篇之十三 函式指標精彩

    回撥函式定義     回撥是通過函式引數傳遞到其它程式碼內的某一段可執行程式碼。或者說,凡是自己定義又主動傳給其他模組呼叫的,都是回撥。回撥允許底層模組呼叫高層定義的子程式。 理解回撥首先要明白什麼是層次/模組,軟體模組是廣義概念,可以包括功能庫(也稱SDK)、C++物件

    Lua 入門學習教程(二) 函式 函式

    還記得開始學C語言的時候,書上就拿兩個數相加 作為例子,來介紹函式。我也拿 Add 來說吧。 函式的簡單寫法就像下面的Add local function Add( a,b ) -- body print(a+b) end Add(10,20) 然後面向物件 loca

    java的反射函式(二)

    所謂回撥,就是客戶程式C呼叫服務程式S中的某個函式A,然後S又在某個時候反過來呼叫C中的某個函式B,對於C來說,這個B便叫做回撥函式。例如Win32下的視窗過程函式就是一個典型的回撥函式。一般說來,C不會自己呼叫B,C提供B的目的就是讓S來呼叫它,而且是C不得不提供。由於S

    WebService、Ajax函式(一)

           本例項演示藉助WebService、Ajax技術和回撥函式,從MSSQL資料庫中獲取所需資料,並用JavaScript語言將資料結果顯示到網頁地圖上。 1、WebService        (1)在工具箱的Ajax Extentions下面找到ScriptM

    Spring學習21Bean生命週期:初始化和銷燬

    實現Bean初始化回撥和銷燬回撥各有三種方法,一是實現介面方法,二是在XML配置,三是使用註解 初始化回撥 1、使用介面 org.springframework.beans.factory.InitializingBean介面類的作用是:在容器設定

    js中的函式封裝,函式實現的簡單動畫效果

                       js實現的簡單動畫效果 一、js實現的簡單動畫       1、此程式碼中運用了js中的建構函式,函式封裝,回撥函式,函式內的正負值的判            

    函式呼叫之函式

    重新回到CSDN,工作以來寫第一個部落格。不碼程式碼,不追求高大上的專業術語,只求通俗的理解。 以前聽過回撥函式,也研究過,但由於沒有在實際中用過,所以也沒太懂,每次一聽到回撥函式這個詞,感覺很高大上,最近在工作上遇到了,而且被公司前輩廣而用之,這說明這個東西並不高大上,反