對於不少初學者來講,每每以爲回調函數很神祕,很想知道回調函數的工做原理。本文將要解釋什麼是回調函數、它們有什麼好處、爲何要使用它們等等問題,在開始以前,假設你已經熟知了函數指針。
什麼是回調函數?
簡而言之,回調函數就是一個經過函數指針調用的函數。若是你把函數的指針(地址)做爲參數傳遞給另外一個函數,當這個指針被用爲調用它所指向的函數時,咱們就說這是回調函數。
爲何要使用回調函數?
由於能夠把調用者與被調用者分開。調用者不關心誰是被調用者,全部它需知道的,只是存在一個具備某種特定原型、某些限制條件(如返回值爲int)的被調用函數。
若是想知道回調函數在實際中有什麼做用,先假設有這樣一種狀況,咱們要編寫一個庫,它提供了某些排序算法的實現,如冒泡排序、快速排序、shell排 序、shake排序等等,但爲使庫更加通用,不想在函數中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,想讓庫可用於多種數據類型(int、 float、string),此時,該怎麼辦呢?可使用函數指針,並進行回調。
回調可用於通知機制,例如,有時要在程序中設置一個 計時器,每到必定時間,程序會獲得相應的通知,但通知機制的實現者對咱們的程序一無所知。而此時,就需有一個特定原型的函數指針,用這個指針來進行回調, 來通知咱們的程序事件已經發生。實際上,SetTimer() API使用了一個回調函數來通知計時器,並且,萬一沒有提供回調函數,它還會把一個消息發往程序的消息隊列。
另外一個使用回調機制的 API函數是EnumWindow(),它枚舉屏幕上全部的頂層窗口,爲每一個窗口調用一個程序提供的函數,並傳遞窗口的處理程序。若是被調用者返回一個 值,就繼續進行迭代,不然,退出。EnumWindow()並不關心被調用者在何處,也不關心被調用者用它傳遞的處理程序作了什麼,它只關心返回值,由於 基於返回值,它將繼續執行或退出。
無論怎麼說,回調函數是繼續自C語言的,於是,在C++中,應只在與C代碼創建接口,或與已有的回調接口打交道時,才使用回調函數。除了上述狀況,在C++中應使用虛擬方法或函數符(functor),而不是回調函數。
下面是本身寫的一個簡單的回調函數,相比其餘的那些複雜的代碼,這個更容易理解:html
#include<stdio.h>
#include<stdlib.h>
void perfect(int n)
{
int i=1;
int count=0;
for(i=1;i<n;i++)
{
if(0==n%i)
{
count+=i;
}
}
if(count==n)
printf("%d是完數\n",n);
else printf("%d不是完數\n",n);
}
void myCallback(void (*perfect)(int ),int n)
{
perfect(n);
}linux
int main()
{
int n;
printf("請輸入一個正整數\n");
scanf("%d",&n);ios
myCallback(perfect,n);
return 0;
}c++
今天討論下C/C++中的回調函數。程序員
在理解「回調函數」以前,首先討論下函數指針的概念。正則表達式
函數指針算法
(1)概念:指針是一個變量,是用來指向內存地址的。一個程序運行時,全部和運行 相關的物件都是須要加載到內存中,這就決定了程序運行時的任何物件均可以用指針來指向它。函數是存放在內存代碼區域內的,它們一樣有地址,所以一樣能夠用 指針來存取函數,把這種指向函數入口地址的指針稱爲函數指針。shell
(2)先來看一個Hello World程序:編程
int main(int argc,char* argv[])
{
printf("Hello World!\n");
return 0;
}
而後,採用函數調用的形式來實現:數組
void Invoke(char* s);
int main(int argc,char* argv[])
{
Invoke("Hello World!\n");
return 0;
}
void Invoke(char* s)
{
printf(s);
}
用函數指針的方式來實現:
void Invoke(char* s);
int main()
{
void (*fp)(char* s); //聲明一個函數指針(fp)
fp=Invoke; //將Invoke函數的入口地址賦值給fp
fp("Hello World!\n"); //函數指針fp實現函數調用
return 0;
}
void Invoke(char* s)
{
printf(s);
}
由上知道:函數指針函數的聲明之間惟一區別就是,用指針名(*fp)代替了函數名Invoke,這樣這聲明瞭一個函數指針,而後進行賦值fp=Invoke就能夠進行函數指針的調用了。聲明函數指針時,只要函數返回值類型、參數個數、參數類型等保持一致,就能夠聲明一個函數指針了。注意,函數指針必須用括號括起來 void (*fp)(char* s)。
實際中,爲了方便,一般用宏定義的方式來聲明函數指針,實現程序以下:
typedef void (*FP)(char* s);
void Invoke(char* s);
int main(int argc,char* argv[])
{
FP fp; //一般是用宏FP來聲明一個函數指針fp
fp=Invoke;
fp("Hello World!\n");
return 0;
}
void Invoke(char* s)
{
printf(s);
}
函數指針數組
下面用程序對函數指針數組來個大體瞭解:
#include <iostream>
#include <string>
using namespace std;
typedef void (*FP)(char* s);
void f1(char* s){cout<<s;}
void f2(char* s){cout<<s;}
void f3(char* s){cout<<s;}
int main(int argc,char* argv[])
{
void* a[]={f1,f2,f3}; //定義了指針數組,這裏a是一個普通指針
a[0]("Hello World!\n"); //編譯錯誤,指針數組不能用下標的方式來調用函數
FP f[]={f1,f2,f3}; //定義一個函數指針的數組,這裏的f是一個函數指針
f[0]("Hello World!\n"); //正確,函數指針的數組進行下標操做能夠進行函數的間接調用
return 0;
}
回調函數
(1)概念:回調函數,顧名思義,就是使用者本身定義一個函數,使用者本身實現這個函數的程序內容,而後把這個函數做爲參數傳入別人(或系統)的函數中,由別人(或系統)的函數在運行時來調用的函數。函數是你實現的,但由別人(或系統)的函數在運行時經過參數傳遞的方式調用,這就是所謂的回調函數。簡單來講,就是由別人的函數運行期間來回調你實現的函數。
(2)標準Hello World程序:
int main(int argc,char* argv[])
{
printf("Hello World!\n");
return 0;
}
將它修改爲函數回調樣式:
//定義回調函數
void PrintfText()
{
printf("Hello World!\n");
}
//定義實現回調函數的"調用函數"
void CallPrintfText(void (*callfuct)())
{
callfuct();
}
//在main函數中實現函數回調
int main(int argc,char* argv[])
{
CallPrintfText(PrintfText);
return 0;
}
修改爲帶參的回調樣式:
//定義帶參回調函數
void PrintfText(char* s)
{
printf(s);
}
//定義實現帶參回調函數的"調用函數"
void CallPrintfText(void (*callfuct)(char*),char* s)
{
callfuct(s);
}
//在main函數中實現帶參的函數回調
int main(int argc,char* argv[])
{
CallPrintfText(PrintfText,"Hello World!\n");
return 0;
}
當代碼量比較小或者需求固定的時候,能夠在一個函數裏綁定另外一個函數,實現函數互調。但當須要常常改變函數或須要實現動態調用時,綁定的參量就不能實現。這時候須要用到函數指針和函數回調
回調函數:回調函數是一個不顯式調用的函數,經過將回調函數的地址傳給調用者從而實現調用
函數指針:指向函數的指針,能夠把函數指針傳入另外一個函數做爲形參,實現回調,首先聲明指針
void f();//這是一個函數原型,無輸入,輸出void型
void (*)()//左邊圓括弧中的星號是函數指針聲明的關鍵,另外兩個元素是函數的返回類型(void)和由邊圓括弧中的入口參數,注意尚未建立函數指針
unsigned psize = sizeof (void (*) ()); // 得到函數指針的大小
void (*p) (); //聲明指針,p是指向函數的指針,該函數無輸入,返回值的類型爲void。左邊圓括弧裏星號後的就是指針變量名。有了指針變量即可以賦值,
void func()
{
//do something
}
p = func; //p的賦值能夠不一樣,但必定要是函數的地址,而且署名和返回類型相同。
傳遞迴調函數的地址給調用者:如今能夠將p傳遞給另外一個函數(調用者) caller(),它將調用p指向的函數,而此函數名是未知的:
void caller(void (*fnp) ())
{
fnp();
}
void func();
int main()
{
p = func;
caller(p); //傳遞函數地址到調用者
}
若是賦了不一樣的值給p(不一樣函數地址),那麼調用者將調用不一樣地址的函數。賦值能夠發生在運行時,這樣使你能實現動態綁定。
值的內容是署名匹配的函數名和返回類型。例如:創建指針變量,只是聲明瞭變量類型。目前能夠用這個變量類型來建立類型定義名及用sizeof表達式得到函數指針的大小:
from 清水河畔
[日期:2014-06-10] | 來源:Linux社區 做者:Linux | [字體:大 中 小] |
1.回調函數的說明:
在進行軟件開發的過程當中,常會用到一些聲明爲CALLBACK的函數,這些函數就是回調函數。使用回調函數能夠改善軟件的結構、提升軟件的複用性。 好比,在一個規模較大的軟件項目中,能夠將一些資或相對獨立的處理模塊封裝到動態鏈接庫(DLL) 中,而後經過回調函數在不一樣的場合來使用這些資源和模塊。利用回調函數還能夠進行程序間複雜的通訊,實現一些通知的功能,在某些場合它是比消息更合適的一 種方式;在一些特殊的狀況下,回調函數更有不可替代的做用。Win32 API 中有許多回調函數的應用,在進行軟件設計時也會常常用到這種函數,而有些時候則須要編寫本身的回調函數。所以,理解回調函數的原理並掌握它的基本用法是非 常必要的。
C ++ 是當代使用最普遍的語言,從嵌入式系統到大型機系統、從Linux到Windows,在大型系統的編制中,處處都是它的身影。它以高效和易編程性得到了許 多資深程序員的信賴。在DirectX Play 開發過程當中,常常須要使用到回調函數,直接使用回調函數顯得複雜麻煩,採用用C + + 實現對回調函數的封裝, 使回調函數變得方便實用,對於DirectX Play 等編程就顯得是很是有意義的。
回調函數簡單講就是一個函數指針。寫一個函數,而後把函數地址傳遞給這個函數指針就能夠了。
回調函數的原形對C ++ 的成員函數用作回調函數的影響是什麼?
編寫回調函數簡單地說就是函數原形一致。函數的輸入參數,輸出參數一致 是很容易保證的。要注意調用類型一致性。函數傳參數有好幾種類型,搞錯了傳參數的方式,系統必然運行錯誤。通常來講都是WINAPI 傳參數方式。要注意C ++ 的類的成員函數和通常的C 函數的區別。C + + 類採用this 規則傳遞函數。在使用類的成員函數做爲回調函數,要求該成員函數被聲名爲靜態成員函數,而且注意函數聲名的時候要同時聲明好參數傳遞規則。
2.個人回調函數的理解,
模塊A ,模塊B,若是模塊B 中調用模塊A 的東西, 在模塊A中發生一個事件或操做,調用B的函數處理,這就才用了回調函數的機制。
C++ Primer Plus 第6版 中文版 清晰有書籤PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm
讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm
讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm
讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm
C++11 獲取系統時間庫函數 time since epoch http://www.linuxidc.com/Linux/2014-03/97446.htm
C++11中正則表達式測試 http://www.linuxidc.com/Linux/2012-08/69086.htm
3.例子的實現
模塊A的代碼
#ifndef __A_H
#define __A_H
class A
{
public:
A(void){}
public:
~A(void){}
typedef void (*perfect)(int ); //聲明回調函數
public:
void CallBackFunc(void (*perfect)(int ),int n) //給模塊B調用的函數
{
perfect(n); //調用的函數
}
};
#endif //__A_H
模塊B的代碼
#include <iostream>
#include "A.h"
using namespace std;
void perfect(int n) //這個函數要求是全局的,或者是類中的靜態成員變量
{
cout<<n<<endl;
}
int main()
{
A a;
a.CallBackFunc(perfect,100); //調用模塊A的代碼。
return 0;
}
靜態成員函數調用非靜態成員函數 應用於回調函數
代碼以下
模塊A的代碼
#ifndef __CALLBACKTEST_H
#define __CALLBACKTEST_H
class CallBackTest
{
public:
CallBackTest(void);
public:
~CallBackTest(void);
typedef void (*perfect)(void*,int ); //聲明回調函數
public:
void CallBackFunc(void* pThisOBject,void (*perfect)(void*,int ),int n)
{
p=perfect;
m_n=n;
m_pThisObject=pThisOBject;
}
void ExecBackFunc()
{
p(m_pThisObject,m_n);
}
private:
perfect p;
int m_n;
void* m_pThisObject;
};
#endif //__CALLBACKTEST_H
模塊B的代碼
#include <iostream>
#include "CallBackTest.h"
using namespace std;
class testMai
{
public:
static void perfect(void *pdata,int n)
{
testMai* pObject=(testMai*)pdata;
pObject->test(n);
}
void Exec()
{
CallBackTest callbackTest;
callbackTest.CallBackFunc(&this,perfect,100);
cout<<"ni hao"<<endl;
callbackTest.ExecBackFunc();
}
void test(int n)
{
int i=1;
int count=0;
m=9;
for(i=1;i<n;i++)
{
if(0==n%i)
{
count+=i;
}
}
if(count==n)
//printf("%d是完數\n",n);
{
cout<<n<<"是完數"<<endl;
}
else
{
cout<<n<<"bu shi de "<<endl;
}
}
private:
int m;
};
int main() { testMai testMai; testMai.Exec(); return 0; }