提升編程能力的C語言技巧總結(jié)
一、函數(shù)指針
在講回調(diào)函數(shù)之前,我們需要了解函數(shù)指針。
我們都知道,C語言的靈魂是指針,我們經(jīng)常使用整型指針,字符串指針,結(jié)構(gòu)體指針等
int *p1; char *p2; STRUCT *p3; //STRUCT為我們定義的結(jié)構(gòu)體
但是好像我們一般很少使用函數(shù)指針,我們一般使用函數(shù)都是直接使用函數(shù)調(diào)用。
下面我們來了解一下函數(shù)指針的概念和使用方法。
1.概念
函數(shù)指針是指向函數(shù)的指針變量。
通常我們說的指針變量是指向一個整型、字符型或數(shù)組等變量,而函數(shù)指針是指向函數(shù)。
函數(shù)指針可以像一般函數(shù)一樣,用于調(diào)用函數(shù)、傳遞參數(shù)。
函數(shù)指針的定義方式為:
函數(shù)返回值類型 (* 指針變量名) (函數(shù)參數(shù)列表);
“函數(shù)返回值類型”表示該指針變量可以指向具有什么返回值類型的函數(shù);“函數(shù)參數(shù)列表”表示該指針變量可以指向具有什么參數(shù)列表的函數(shù)。這個參數(shù)列表中只需要寫函數(shù)的參數(shù)類型即可。
我們看到,函數(shù)指針的定義就是將“函數(shù)聲明”中的“函數(shù)名”改成“(指針變量名)”。但是這里需要注意的是:“(指針變量名)”兩端的括號不能省略,括號改變了運(yùn)算符的優(yōu)先級。如果省略了括號,就不是定義函數(shù)指針而是一個函數(shù)聲明了,即聲明了一個返回值類型為指針型的函數(shù)。
那么怎么判斷一個指針變量是指向變量的指針變量還是指向函數(shù)的指針變量呢?首先看變量名前面有沒有“”,如果有“”說明是指針變量;其次看變量名的后面有沒有帶有形參類型的圓括號,如果有就是指向函數(shù)的指針變量,即函數(shù)指針,如果沒有就是指向變量的指針變量。
最后需要注意的是,指向函數(shù)的指針變量沒有 ++ 和 – 運(yùn)算。
一般為了方便使用,我們會選擇
typedef 函數(shù)返回值類型 (* 指針變量名) (函數(shù)參數(shù)列表);
比如
typedef int (*Fun1)(int);//聲明也可寫成int (*Fun1)(int x),但習(xí)慣上一般不這樣。 typedef int (*Fun2)(int, int);//參數(shù)為兩個整型,返回值為整型 typedef void (*Fun3)(void);//無參數(shù)和返回值 typedef void* (*Fun4)(void*);//參數(shù)和返回值都為void*指針
2.如何用函數(shù)指針調(diào)用函數(shù)
給大家舉一個例子:
int Func(int x); /*聲明一個函數(shù)*/ int (*p) (int x); /*定義一個函數(shù)指針*/ p = Func; /*將Func函數(shù)的首地址賦給指針變量p*/ p = &Func; /*將Func函數(shù)的首地址賦給指針變量p*/
賦值時(shí)函數(shù) Func 不帶括號,也不帶參數(shù)。由于函數(shù)名 Func 代表函數(shù)的首地址,因此經(jīng)過賦值以后,指針變量 p 就指向函數(shù) Func() 代碼的首地址了。
下面來寫一個程序,看了這個程序你們就明白函數(shù)指針怎么使用了:
#include <stdio.h> int Max(int, int); //函數(shù)聲明 int main(void) { int(*p)(int, int); //定義一個函數(shù)指針 int a, b, c; p = Max; //把函數(shù)Max賦給指針變量p, 使p指向Max函數(shù) printf("please enter a and b:"); scanf("%d%d", &a, &b); c = (*p)(a, b); //通過函數(shù)指針調(diào)用Max函數(shù) printf("a = %d\nb = %d\nmax = %d\n", a, b, c); return 0; } int Max(int x, int y) //定義Max函數(shù) { int z; if (x > y) { z = x; } else { z = y; } return z; }
特別注意的是,因?yàn)楹瘮?shù)名本身就可以表示該函數(shù)地址(指針),因此在獲取函數(shù)指針時(shí),可以直接用函數(shù)名,也可以取函數(shù)的地址。
- p = Max可以改成 p = &Max
- c = (*p)(a, b) 可以改成 c = p(a, b)
3.函數(shù)指針作為某個函數(shù)的參數(shù)
既然函數(shù)指針變量是一個變量,當(dāng)然也可以作為某個函數(shù)的參數(shù)來使用的。
示例:
#include <stdio.h> #include <stdlib.h> typedef void(*FunType)(int); //前加一個typedef關(guān)鍵字,這樣就定義一個名為FunType函數(shù)指針類型,而不是一個FunType變量。 //形式同 typedef int* PINT; void myFun(int x); void hisFun(int x); void herFun(int x); void callFun(FunType fp,int x); int main() { callFun(myFun,100);//傳入函數(shù)指針常量,作為回調(diào)函數(shù) callFun(hisFun,200); callFun(herFun,300); return 0; } void callFun(FunType fp,int x) { fp(x);//通過fp的指針執(zhí)行傳遞進(jìn)來的函數(shù),注意fp所指的函數(shù)有一個參數(shù) } void myFun(int x) { printf("myFun: %d\n",x); } void hisFun(int x) { printf("hisFun: %d\n",x); } void herFun(int x) { printf("herFun: %d\n",x); }
輸出:
4.函數(shù)指針作為函數(shù)返回類型
有了上面的基礎(chǔ),要寫出返回類型為函數(shù)指針的函數(shù)應(yīng)該不難了,下面這個例子就是返回類型為函數(shù)指針的函數(shù):
void (* func5(int, int, float ))(int, int) { ... }
在這里, func5 以 (int, int, float) 為參數(shù),其返回類型為 void (\*)(int, int) 。在C語言中,變量或者函數(shù)的聲明也是一個大學(xué)問
5.函數(shù)指針數(shù)組
在開始講解回調(diào)函數(shù)前,最后介紹一下函數(shù)指針數(shù)組。既然函數(shù)指針也是指針,那我們就可以用數(shù)組來存放函數(shù)指針。下面我們看一個函數(shù)指針數(shù)組的例子:
/* 方法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];
上面兩種方法都可以用來定義函數(shù)指針數(shù)組,它們定義了一個元素個數(shù)為5,類型是 *void (\*)(int, int, float)*的函數(shù)指針數(shù)組。
6.函數(shù)指針總結(jié)
函數(shù)指針常量 :Max;函數(shù)指針變量:p;
數(shù)名調(diào)用如果都得如(*myFun)(10)這樣,那書寫與讀起來都是不方便和不習(xí)慣的。所以C語言的設(shè)計(jì)者們才會設(shè)計(jì)成又可允許myFun(10)這種形式地調(diào)用(這樣方便多了,并與數(shù)學(xué)中的函數(shù)形式一樣)。
函數(shù)指針變量也可以存入一個數(shù)組內(nèi)。數(shù)組的聲明方法:int (*fArray[10]) ( int );
二、回調(diào)函數(shù)
1.什么是回調(diào)函數(shù)
我們先來看看百度百科是如何定義回調(diào)函數(shù)的:
回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時(shí),我們就說這是回調(diào)函數(shù)?;卣{(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對該事件或條件進(jìn)行響應(yīng)。
這段話比較長,也比較繞口。下面我通過一幅圖來說明什么是回調(diào):
假設(shè)我們要使用一個排序函數(shù)來對數(shù)組進(jìn)行排序,那么在主程序(Main program)中,我們先通過庫,選擇一個庫排序函數(shù)(Library function)。但排序算法有很多,有冒泡排序,選擇排序,快速排序,歸并排序。同時(shí),我們也可能需要對特殊的對象進(jìn)行排序,比如特定的結(jié)構(gòu)體等。庫函數(shù)會根據(jù)我們的需要選擇一種排序算法,然后調(diào)用實(shí)現(xiàn)該算法的函數(shù)來完成排序工作。這個被調(diào)用的排序函數(shù)就是回調(diào)函數(shù)(Callback function)。
結(jié)合這幅圖和上面對回調(diào)函數(shù)的解釋,我們可以發(fā)現(xiàn),要實(shí)現(xiàn)回調(diào)函數(shù),最關(guān)鍵的一點(diǎn)就是要將函數(shù)的指針傳遞給一個函數(shù)(上圖中是庫函數(shù)),然后這個函數(shù)就可以通過這個指針來調(diào)用回調(diào)函數(shù)了。注意,回調(diào)函數(shù)并不是C語言特有的,幾乎任何語言都有回調(diào)函數(shù)。在C語言中,我們通過使用函數(shù)指針來實(shí)現(xiàn)回調(diào)函數(shù)。
我的理解是:把一段可執(zhí)行的代碼像參數(shù)傳遞那樣傳給其他代碼,而這段代碼會在某個時(shí)刻被調(diào)用執(zhí)行,這就叫做回調(diào)。
如果代碼立即被執(zhí)行就稱為同步回調(diào),如果過后再執(zhí)行,則稱之為異步回調(diào)。
回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時(shí),我們就說這是回調(diào)函數(shù)。
回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對該事件或條件進(jìn)行響應(yīng)。
2.為什么要用回調(diào)函數(shù)
因?yàn)榭梢园颜{(diào)用者與被調(diào)用者分開,所以調(diào)用者不關(guān)心誰是被調(diào)用者。它只需知道存在一個具有特定原型和限制條件的被調(diào)用函數(shù)。
簡而言之,回調(diào)函數(shù)就是允許用戶把需要調(diào)用的方法的指針作為參數(shù)傳遞給一個函數(shù),以便該函數(shù)在處理相似事件的時(shí)候可以靈活的使用不同的方法。
int Callback() ///< 回調(diào)函數(shù) { // TODO return 0; } int main() ///< 主函數(shù) { // TODO Library(Callback); ///< 庫函數(shù)通過函數(shù)指針進(jìn)行回調(diào) // TODO return 0; }
回調(diào)似乎只是函數(shù)間的調(diào)用,和普通函數(shù)調(diào)用沒啥區(qū)別。
但仔細(xì)看,可以發(fā)現(xiàn)兩者之間的一個關(guān)鍵的不同:在回調(diào)中,主程序把回調(diào)函數(shù)像參數(shù)一樣傳入庫函數(shù)。
這樣一來,只要我們改變傳進(jìn)庫函數(shù)的參數(shù),就可以實(shí)現(xiàn)不同的功能,這樣有沒有覺得很靈活?并且當(dāng)庫函數(shù)很復(fù)雜或者不可見的時(shí)候利用回調(diào)函數(shù)就顯得十分優(yōu)秀。
3.怎么使用回調(diào)函數(shù)
int Callback_1(int a) ///< 回調(diào)函數(shù)1 { printf("Hello, this is Callback_1: a = %d ", a); return 0; } int Callback_2(int b) ///< 回調(diào)函數(shù)2 { printf("Hello, this is Callback_2: b = %d ", b); return 0; } int Callback_3(int c) ///< 回調(diào)函數(shù)3 { printf("Hello, this is Callback_3: c = %d ", c); return 0; } int Handle(int x, int (*Callback)(int)) ///< 注意這里用到的函數(shù)指針定義 { Callback(x); } int main() { Handle(4, Callback_1); Handle(5, Callback_2); Handle(6, Callback_3); return 0; }
如上述代碼:可以看到,Handle()函數(shù)里面的參數(shù)是一個指針,在main()函數(shù)里調(diào)用Handle()函數(shù)的時(shí)候,給它傳入了函數(shù)Callback_1()/Callback_2()/Callback_3()的函數(shù)名,這時(shí)候的函數(shù)名就是對應(yīng)函數(shù)的指針,也就是說,回調(diào)函數(shù)其實(shí)就是函數(shù)指針的一種用法。
4.簡單回調(diào)函數(shù)示例
下面是一個四則運(yùn)算的簡單回調(diào)函數(shù)例子
#include <stdio.h> #include <stdlib.h> /**************************************** * 函數(shù)指針結(jié)構(gòu)體 ***************************************/ typedef struct _OP { float (*p_add)(float, float); float (*p_sub)(float, float); float (*p_mul)(float, float); float (*p_div)(float, float); } OP; /**************************************** * 加減乘除函數(shù) ***************************************/ 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; } /**************************************** * 初始化函數(shù)指針 ***************************************/ void init_op(OP *op) { op->p_add = ADD; op->p_sub = SUB; op->p_mul = &MUL; op->p_div = &DIV; } /**************************************** * 庫函數(shù) ***************************************/ 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); /* 直接使用函數(shù)指針調(diào)用函數(shù) */ 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)); /* 調(diào)用回調(diào)函數(shù) */ 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; }
5. 回調(diào)函數(shù)實(shí)例(很有用)
一個GPRS模塊聯(lián)網(wǎng)的小項(xiàng)目,使用過的同學(xué)大概知道2G、4G、NB等模塊要想實(shí)現(xiàn)無線聯(lián)網(wǎng)功能都需要經(jīng)歷模塊上電初始化、注冊網(wǎng)絡(luò)、查詢網(wǎng)絡(luò)信息質(zhì)量、連接服務(wù)器等步驟,這里的的例子就是,利用一個狀態(tài)機(jī)函數(shù)(根據(jù)不同狀態(tài)依次調(diào)用不同實(shí)現(xiàn)方法的函數(shù)),通過回調(diào)函數(shù)的方式依次調(diào)用不同的函數(shù),實(shí)現(xiàn)模塊聯(lián)網(wǎng)功能,如下:
/********* 工作狀態(tài)處理 *********/ typedef struct { uint8_t mStatus; uint8_t (* Funtion)(void); //函數(shù)指針的形式 } M26_WorkStatus_TypeDef; //M26的工作狀態(tài)集合調(diào)用函數(shù) /********************************************** ** >M26工作狀態(tài)集合函數(shù) ***********************************************/ M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] = { {GPRS_NETWORK_CLOSE, M26_PWRKEY_Off }, //模塊關(guān)機(jī) {GPRS_NETWORK_OPEN, M26_PWRKEY_On }, //模塊開機(jī) {GPRS_NETWORK_Start, M26_Work_Init }, //管腳初始化 {GPRS_NETWORK_CONF, M26_NET_Config }, /AT指令配置 {GPRS_NETWORK_LINK_CTC, M26_LINK_CTC }, //連接調(diào)度中心 {GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC }, //等待調(diào)度中心回復(fù) {GPRS_NETWORK_LINK_FEM, M26_LINK_FEM }, //連接前置機(jī) {GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM }, //等待前置機(jī)回復(fù) {GPRS_NETWORK_COMM, M26_COMM }, //正常工作 {GPRS_NETWORK_WAIT_Sig, M26_WAIT_Sig }, //等待信號回復(fù) {GPRS_NETWORK_GetSignal, M26_GetSignal }, //獲取信號值 {GPRS_NETWORK_RESTART, M26_RESET }, //模塊重啟 } /********************************************** ** >M26模塊工作狀態(tài)機(jī),依次調(diào)用里面的12個函數(shù) ***********************************************/ uint8_t M26_WorkStatus_Call(uint8_t Start) { uint8_t i = 0; for(i = 0; i < 12; i++) { if(Start == M26_WorkStatus_Tab[i].mStatus) { return M26_WorkStatus_Tab[i].Funtion(); } } return 0; }
所以,如果有人想做個NB模塊聯(lián)網(wǎng)項(xiàng)目,可以copy上面的框架,只需要修改回調(diào)函數(shù)內(nèi)部的具體實(shí)現(xiàn),或者增加、減少回調(diào)函數(shù),就可以很簡潔快速的實(shí)現(xiàn)模塊聯(lián)網(wǎng)。
到此這篇關(guān)于提升編程能力的C語言技巧總結(jié)的文章就介紹到這了,更多相關(guān)C語言技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于matlab MFCC+GMM的安全事件聲學(xué)檢測系統(tǒng)
這篇文章主要為大家介紹了基于matlab MFCC+GMM的安全事件聲學(xué)檢測系統(tǒng)實(shí)現(xiàn)及源碼示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02C語言循環(huán)隊(duì)列與用隊(duì)列實(shí)現(xiàn)棧問題解析
循環(huán)隊(duì)列又叫環(huán)形隊(duì)列,是一種特殊的隊(duì)列。循環(huán)隊(duì)列解決了隊(duì)列出隊(duì)時(shí)需要將所有數(shù)據(jù)前移一位的問題,本篇帶你一起看看循環(huán)隊(duì)列的問題和怎樣用隊(duì)列實(shí)現(xiàn)棧2022-04-04OpenCV使用BSM統(tǒng)計(jì)視頻中移動的對象
這篇文章主要為大家詳細(xì)介紹了OpenCV如何使用BackgroundSubstractor(BSM)實(shí)現(xiàn)視頻中移動對象統(tǒng)計(jì)功能,文中的示例代碼講解詳細(xì),需要的可以參考一下2023-02-02C++實(shí)現(xiàn)LeetCode(19.移除鏈表倒數(shù)第N個節(jié)點(diǎn))
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(19.移除鏈表倒數(shù)第N個節(jié)點(diǎn)),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++ vector擴(kuò)容解析noexcept應(yīng)用場景
這篇文章主要介紹了C++ vector擴(kuò)容解析noexcept應(yīng)用場景,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09