一文搞懂C語言回撥函式
什麼是回撥函式
我們先來看看百度百科是如何定義回撥函式的:
回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該事件或條件進行響應。
這段話比較長,也比較繞口。下面我通過一幅圖來說明什麼是回撥:
假設我們要使用一個排序函式來對陣列進行排序,那麼在主程式(Main program)中,我們先通過庫,選擇一個庫排序函式(Library function)。但排序演算法有很多,有氣泡排序,選擇排序,快速排序,歸併排序。同時,我們也可能需要對特殊的物件進行排序,比如特定的結構體等。庫函式會根據我們的需要選擇一種排序演算法,然後呼叫實現該演算法的函式來完成排序工作。這個被呼叫的排序函式就是回撥函式(Callback function)。
結合這幅圖和上面對回撥函式的解釋,我們可以發現,要實現回撥函式,最關鍵的一點就是要將函式的指標傳遞給一個函式(上圖中是庫函式),然後這個函式就可以通過這個指標來呼叫回撥函數了。注意,回撥函式並不是C語言特有的,幾乎任何語言都有回撥函式。在C語言中,我們通過使用函式指標來實現回撥函式。那函式指標是什麼?不著急,下面我們就先來看看什麼是函式指標。
什麼是函式指標
函式指標也是一種指標,只是它指向的不是整型,字元型而是函式。在C中,每個函式在編譯後都是儲存在記憶體中,並且每個函式都有一個入口地址,根據這個地址,我們便可以訪問並使用這個函式。函式指標就是通過指向這個函式的入口,從而呼叫這個函式。
函式指標的使用
函式指標的定義
函式指標雖然也是指標,但它的定義方式卻和其他指標看上去很不一樣,我們來看看它是如何定義的:
/* 方法1 */
void (*p_func)(int, int, float) = NULL;
/* 方法2 */
typedef void (*tp_func)(int, int, float);
tp_func p_func = NULL;
這兩種方式都是定義了一個指向返回值為 void
型別,引數為 (int, int, float)
的函式指標。第二種方法是為了讓函式指標更容易理解,尤其是在複雜的環境下;而對於一般的函式指標,直接用第一種方法就行了。
如果之前沒見過函式指標,可能會覺得函式指標的定義比較怪,為什麼不是 void ()(int, int, float) *p_func
void (*p_func)(int, int, float)
這種形式?這個問題我也不知道,也沒必要糾結,花點時間理解下它與普通指標的區別,實在不行就先記住它的形式。
函式指標的賦值
在定義完函式指標後,我們就需要給它賦值了我們有兩種方式對函式指標進行賦值:
void (*p_func)(int, int, float) = NULL;
p_func = &func1;
p_func = func2;
上面兩種方法都是合法的,對於第二種方法,編譯器會隱式地將 func_2
由 void ()(int, int, float)
型別轉換成 void (*)(int, int, float)
型別,因此,這兩種方法都行。想要了解更詳細的說明,可以看看下面這個stackoverflow的連結。
使用函式指標呼叫函式
因為函式指標也是指標,因此可以使用常規的帶 *
的方法來呼叫函式。和函式指標的賦值一樣,我們也可以使用兩種方法:
/* 方法1 */
int val1 = p_func(1,2,3.0);
/* 方法2 */
int val2 = (*p_func)(1,2,3.0);
方法1和我們平時直接呼叫函式是一樣的,方法2則是用了 *
對函式指標取值,從而實現對函式的呼叫。
將函式指標作為引數傳給函式
函式指標和普通指標一樣,我們可以將它作為函式的引數傳遞給函式,下面我們看看如何實現函式指標的傳參:
/* func3 將函式指標 p_func 作為其形參 */
void func3(int a, int b, float c, void (*p_func)(int, int, float))
{
(*p_func)(a, b, c);
}
/* func4 呼叫函式func3 */
void func4()
{
func3(1, 2, 3.0, func_1);
/* 或者 func3(1, 2, 3.0, &func_1); */
}
函式指標作為函式返回型別
有了上面的基礎,要寫出返回型別為函式指標的函式應該不難了,下面這個例子就是返回型別為函式指標的函式:
void (* func5(int, int, float ))(int, int)
{
...
}
在這裡, func5
以 (int, int, float)
為引數,其返回型別為 void (*)(int, int)
。在C語言中,變數或者函式的宣告也是一個大學問,想要了解更多關於宣告的話題,可以參考我之前的文章 - C專家程式設計》讀書筆記(1-3章)。這本書的第三章花了整整一章的內容來講解如何讀懂C語言的宣告。
函式指標陣列
在開始講解回撥函式前,最後介紹一下函式指標陣列。既然函式指標也是指標,那我們就可以用陣列來存放函式指標。下面我們看一個函式指標陣列的例子:
/* 方法1 */
void (*func_array_1[5])(int, int, float);
/* 方法2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];
上面兩種方法都可以用來定義函式指標陣列,它們定義了一個元素個數為5,型別是 void (*)(int, int, float)
的函式指標陣列。
回撥函式
我們前面談的都是函式指標,現在我們回到正題,來看看回調函式到底是怎樣實現的。下面是一個四則運算的簡單回撥函式例子:
#include <stdio.h>
#include <stdlib.h>
/****************************************
* 函式指標結構體
***************************************/
typedef struct _OP {
float (*p_add)(float, float);
float (*p_sub)(float, float);
float (*p_mul)(float, float);
float (*p_div)(float, float);
} OP;
/****************************************
* 加減乘除函式
***************************************/
float ADD(float a, float b)
{
return a + b;
}
float SUB(float a, float b)
{
return a - b;
}
float MUL(float a, float b)
{
return a * b;
}
float DIV(float a, float b)
{
return a / b;
}
/****************************************
* 初始化函式指標
***************************************/
void init_op(OP *op)
{
op->p_add = ADD;
op->p_sub = SUB;
op->p_mul = &MUL;
op->p_div = &DIV;
}
/****************************************
* 庫函式
***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
return (*op_func)(a, b);
}
int main(int argc, char *argv[])
{
OP *op = (OP *)malloc(sizeof(OP));
init_op(op);
/* 直接使用函式指標呼叫函式 */
printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2),
(op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));
/* 呼叫回撥函式 */
printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",
add_sub_mul_div(1.3, 2.2, ADD),
add_sub_mul_div(1.3, 2.2, SUB),
add_sub_mul_div(1.3, 2.2, MUL),
add_sub_mul_div(1.3, 2.2, DIV));
return 0;
}
這個例子有點長,我一步步地來講解如何使用回撥函式。
第一步
要完成加減乘除,我們需要定義四個函式分別實現加減乘除的運算功能,這幾個函式就是:
/****************************************
* 加減乘除函式
***************************************/
float ADD(float a, float b)
{
return a + b;
}
float SUB(float a, float b)
{
return a - b;
}
float MUL(float a, float b)
{
return a * b;
}
float DIV(float a, float b)
{
return a / b;
}
第二步
我們需要定義四個函式指標分別指向這四個函式:
/****************************************
* 函式指標結構體
***************************************/
typedef struct _OP {
float (*p_add)(float, float);
float (*p_sub)(float, float);
float (*p_mul)(float, float);
float (*p_div)(float, float);
} OP;
/****************************************
* 初始化函式指標
***************************************/
void init_op(OP *op)
{
op->p_add = ADD;
op->p_sub = SUB;
op->p_mul = &MUL;
op->p_div = &DIV;
}
第三步
我們需要建立一個“庫函式”,這個函式以函式指標為引數,通過它來呼叫不同的函式:
/****************************************
* 庫函式
***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
return (*op_func)(a, b);
}
第四步
當這幾部都完成後,我們就可以開始呼叫回撥函數了:
/* 呼叫回撥函式 */
printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",
add_sub_mul_div(1.3, 2.2, op->p_add),
add_sub_mul_div(1.3, 2.2, op->p_sub),
add_sub_mul_div(1.3, 2.2, MUL),
add_sub_mul_div(1.3, 2.2, DIV));
簡單的四部便可以實現回撥函式。在這四步中,我們甚至可以省略第二步,直接將函式名傳入“庫函式”,比如上面的乘法和除法運算。回撥函式的核心就是函式指標,只要搞懂了函式指標再學回調函式,那真是手到擒來了。
總結
本文主要講了如何使用函式指標和回撥函式。回撥函式的核心就是函式指標,因此我花了大量篇幅講解函式指標。對於回撥函式的實現,我給出了一個例子,希望這個例子能給你幫助。回撥函式很重要,如果連它都不會,C語言真不算入門了。當然了,即使會了它,也不要驕傲,因為C語言還有太多的東西需要我們去學習、實踐。
相關推薦
一文搞懂C語言回撥函式
什麼是回撥函式我們先來看看百度百科是如何定義回撥函式的:回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由
一文搞懂C/C++中指標那些事(上篇)
一 指標變數 1.間接存取 指標變數的值為地址;普通變數的值為資料;其中“*”為指標運算子。&是地址操作符,用來引用一個記憶體地址。通過在變數名字前使用&操作符,我們可以得到該變數的記憶體地址。 針對記憶體資料的
C語言回撥函式熟練—使用方法(構建程式框架方便好用)
通俗點不行嗎?啊,不行嗎?老外把國人玩的都不是人了。國人還自己玩自己。非把一個簡單的東西複雜化。叫那麼難理解!!窩裡鬥。。。。。。典型!!!!!!!! 不說那麼複雜的,誰是狗屎,豬屎。就說怎麼用回撥。使用步驟: 1.寫一個函式A,A裡面有一個引數是個指標函式 比如: int shao(in
C語言——回撥函式
C語言——回撥函式 宗旨:技術的學習是有限的,分享的精神是無限的。 如果引數是一個函式指標,呼叫者可以傳遞一個函式的地址給實現者,讓實現者去呼叫它,這就是回撥函式。 void func(void (*f)(void*), void *p) 提供一個回撥
C 語言回撥函式
1.回撥函式:回撥函式是指 使用者自己定義一個函式,實現這個函式的程式內容,然後把這個函式(入口地址)作為引數傳入別的函式中,由別的函式在執行時來呼叫的函式。函式是你實現的,但由別人(或系統)的函式在執行時通過引數傳遞的方式呼叫,這就是所謂的回撥函式。簡單來說,就是由別人的函式執行期間來回調你實現的
c語言回撥函式 -----qsort
回撥函式就是一個通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於
C語言回撥函式的定義和寫法
1 定義和使用場合 回撥函式是指 使用者自己定義一個函式,實現這個函式的程式內容,然後把這個函式(入口地址)作為引數傳入別人(或系統)的函式中,由別人(或系統)的函式在執行時來呼叫的函式。函式是你實現的,但由別人(或系統)的函式在執行時通過引數傳遞的方式呼叫,這就是所謂的回撥函式。簡單來說,就是由別人的函式
C語言回撥函式一個簡單的例子
原文地址:http://blog.csdn.net/zgrjkflmkyc/article/details/9198519 回撥函式在linux核心或是微控制器上應用得太多,由此也可以大致判斷,一個初學者和有個有經驗的區別。我轉寫在這兒,希望更多的人能夠看到,一起進步!!
一文搞懂:詞法作用域、動態作用域、回撥函式、閉包
不管什麼語言,我們總要學習作用域(或生命週期)的概念,比如常見的稱呼:全域性變數、包變數、模組變數、本地變數、區域性變數等等。不管如何稱呼這些作用域的範圍,實現它們的目的都一樣: (1)為了避免名稱衝突; (2)為了限定變數的生命週期(本文以變數名說事,其它的名稱在規則上是一樣的)
「詞嵌入」在自然語言處理中扮演什麼角色?一文搞懂Word Embeddings的背後原理
原文來源:DATASCIENCE 作者:Ruslana Dalinina 「機器人圈」編譯:嗯~阿童木呀、多啦A亮 「機器人圈」正式更名為「雷克世界」,後臺回覆「雷克世界」檢視更多詳
一文搞懂各種 Docker 網絡 - 每天5分鐘玩轉 Docker 容器技術(72)
docker 教程 容器 前面各小節我們先後學習了 Docker Overaly,Macvaln,Flannel,Weave 和 Calico 跨主機網絡方案。目前這個領域是百家爭鳴,而且還有新的方案不斷湧現。本節將從不同維度比較各種網絡方案,大家在選擇的時候可以參考。CloudMan 的建議是:
一文搞懂 Java 線程中斷
回復 代碼 信號 過程 執行 except 實例 二維 微信公眾 在之前的一文《如何"優雅"地終止一個線程》中詳細說明了 stop 終止線程的壞處及如何優雅地終止線程,那麽還有別的可以終止線程的方法嗎?答案是肯定的,它就是我們今天要分享的——線程中斷。 下面的這斷代碼大家應
C語言-回撥
callback 回撥函式是通過函式指標呼叫的函式。如果你把函式的指標(地址)作為引數傳遞給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由該函式的實現方直接呼叫,而是在特定的事件或條件發生時由另外的一方呼叫的,用於對該
資料結構與演算法隨筆之------二叉樹的遍歷(一文搞懂二叉樹的四種遍歷)
二叉樹的遍歷 二叉樹的遍歷(traversing binary tree)是指從根結點出發,按照某種次序依次訪問二叉樹中所有的結點,使得每個結點被訪問依次且僅被訪問一次。 遍歷分為四種,前序遍歷,中序遍歷,後序遍歷及層序遍歷 前序 中
一文搞懂k近鄰(k-NN)演算法(一)
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
Python裝飾器是一款神奇的神器!你知道怎麼用嗎?一文搞懂它!
進群:548377875 即可獲取小編精心準備的教程以及大量的PDF呢! 1.引子 #功能函式 def add(x,y): return x+y #裝飾函式 def logger(fn): print('frist') x =
一文搞懂交叉熵在機器學習中的使用,透徹理解交叉熵背後的直覺
關於交叉熵在loss函式中使用的理解 交叉熵(cross entropy)是深度學習中常用的一個概念,一般用來求目標與預測值之間的差距。以前做一些分類問題
一文搞懂 db2 的鎖(表鎖、行鎖、共享鎖、排他鎖)
鎖,很好理解,每個人都在自己的房屋上安裝有鎖,你擁有了鎖,房屋只有你能獨佔,別人不能訪問。資料庫中的鎖也一樣,只不過更加細分。 db2 中基本的鎖有兩類: 排他鎖(X鎖),也叫寫鎖,當某行資料正在被修改時,其他程序不能再讀取或修改 共享鎖(S鎖),也叫讀鎖,當某行資料正
一文搞懂 Java 執行緒中斷
在之前的一文《如何"優雅"地終止一個執行緒》中詳細說明了 stop 終止執行緒的壞處及如何優雅地終止執行緒,那麼還有別的可以終止執行緒的方法嗎?答案是肯定的,它就是我們今天要分享的——執行緒中斷。 下面的這斷程式碼大家應該再熟悉不過了,執行緒休眠需要捕獲或者丟擲
一文搞懂Raft演算法
raft是工程上使用較為廣泛的強一致性、去中心化、高可用的分散式協議。在這裡強調了是在工程上,因為在學術理論界,最耀眼的還是大名鼎鼎的Paxos。但Paxos是:少數真正理解的人覺得簡單,尚未理解的人覺得很難,大多數人都是一知半解。本人也花了很多時間、看了很多材料也沒有真正理解。直到看到raft的論文,兩