1、指針函數 指針函數,從名字上看它本質上是一個函數。指針函數:返回值類型是指針的函數。函數聲明如下: int *plusfunction(int a,int b); 當然也可以寫成如下格式: int* plusfunction(int a,int b); 讓指針標誌 * 與int緊貼在一起,而與函 ...
1、指針函數
指針函數,從名字上看它本質上是一個函數。指針函數:返回值類型是指針的函數。函數聲明如下:
int *plusfunction(int a,int b);
當然也可以寫成如下格式:
int* plusfunction(int a,int b);
讓指針標誌 * 與int緊貼在一起,而與函數名f間隔開,這樣看起來就明瞭些了,plusfunction是函數名,返回值類型是一個int類型的指針。
指針函數就是一個普通的函數,普通到僅僅是因為它的函數返回值是指針而已。
#include <stdio.h> #include <stdlib.h> int* plusfunction(int a,int b); int main() { int *p = NULL; p = plusfunction(1,2); printf("*p is %d\n",*p); free(p); return(0); } int* plusfunction(int a,int b) { int *p = (int *) malloc( sizeof(int) ); *p = a + b; return(p); }
這是一個簡單的指針函數的例子,運行結果如下,本文代碼在VScode平臺運行,使用方法《使用VScode調試C語言》。
不過我有個疑問,使用指針函數,和函數入參是指針有什麼好處呢???
#include <stdio.h> #include <stdlib.h> void plusfunction(int a,int b,int *p); int main() { int *p = NULL; p = (int *) malloc(sizeof(int) ); plusfunction(1,2,p); printf("*p is %d\n",*p); free(p); return(0); } void plusfunction(int a,int b,int *p) { *p = a + b; }
這樣執行也是沒問題的啊,當然我也發現了指針函數的好處,就是可以把函數作為另一個函數的入參。
testfunction(plusfunction(1,2));
在這點上用第二種方法,將指針作為函數入參是不行的。
還有,將指針作為函數入參前需要向指針申請記憶體,而指針函數卻不用。
除去這兩點,日常開發中,我還真沒找到指針函數的“優點”,讓我覺得某個功能必須用指針函數實現,或用指針函數實現後代碼更整潔,提高代碼可讀性。
2、函數指針
函數指針,本質上他是一個指針,並不是一個函數。在C語言中有些概念是一脈相承的,之前的推文《指針與數組》,數組指針和指針數組的概念更有效幫你理解函數指針和指針函數。
函數指針說的就是一個指針,但這個指針指向的函數,不是普通的基本數據類型或者類對象。函數指針定義如下:
int (*f)(int a,int b);//聲明函數指針
和指針函數的定義對比可以看到,函數指針與指針函數的最大區別是函數指針的函數名是一個指針,即函數名前面有一個指針類型的標誌型號“*”。
註意指針函數與函數指針表示方法的不同,千萬不要混淆。最簡單的辨別方式就是看函數名前面的指針*號有沒有被括弧()包含,如果被包含就是函數指針,反之則是指針函數。
當然,函數指針的返回值也可以是指針。簡單的函數調用示例
#include <stdio.h> void MyFun(int a); int main() { MyFun(10); return(0); } void MyFun(int a) { printf("a is %d\n",a); }
這是一個再簡單不過的函數調用了,其實他還可以寫作下麵格式
#include <stdio.h> void MyFun(int a); int main() { (*MyFun)(10); return(0); } void MyFun(int a) { printf("a is %d\n",a); }
這個代碼是正常運行的,也就是說(*MyFun)(10);和MyFun(10);是一樣的,在這裡強烈建議沒有看過《指針與數組》的同學,先看一下。
在教材和資料中,都會講到數組名就是指向數組第一個數據的常量指針。從上面例子看到,函數名貌似也是“常量指針”。
數組中,可以將數組名賦給一個指針,然後通過指針訪問數組中的內容,那麼我們就可以定義一個函數指針,將函數名賦給函數指針,通過這個函數指針調用函數。
#include <stdio.h> void MyFun(int a);/* 這個聲明也可寫成:void MyFun( int )*/ void (*FunP)(int);/*也可聲明成void(*FunP)(int x),但習慣上一般不這樣。 */ int main() { FunP = MyFun; *FunP(10); return(0); } void MyFun(int a) { printf("a is %d\n",a); }
在第7行在函數指針前加*相當取指針的值,在這裡理解為將MyFun函數取出。那麼再進一步
#include <stdio.h> void MyFun(int a); /* 這個聲明也可寫成:void MyFun( int )*/ void (*FunP)(int); /*也可聲明成void(*FunP)(int x),但習慣上一般不這樣。 */ int main() { FunP = MyFun; FunP(10); return (0); } void MyFun(int a) { printf("a is %d\n", a); }
是的,將FunP前面的*號拿掉也是可以運行的,上面的示例代碼就是函數指針在C語言中的最常見形態。之前的例子只是為了讓你更能理解函數指針,實際開發中只需要用函數指針的最終,最常見的形態即可。
不然代碼中出現之前的形式,其他程式員並不是很熟悉,就成了“騷操作”,雖然不影響運行,但是降低代碼的可閱讀性。
3、typedef的引入
C語言中typedef關鍵字作用:複雜的聲明定義簡單的別名,很明顯我們上面講述的函數指針就是一個比較複雜的類型,可以使用typedef關鍵字將函數指針的定義簡單化。
#include <stdio.h> void MyFun(int a); /* 這個聲明也可寫成:void MyFun( int )*/ typedef void (*FunType)(int); /*這樣只是定義一個函數指針類型 */ FunType FunP; /*然後用FunType類型來聲明全局FunP變數*/ int main() { FunP = MyFun; FunP(10); return (0); } void MyFun(int a) { printf("a is %d\n", a); }
強烈建議使用typedef和函數指針組合的方式,這是最常見的方式,大家都能看懂的常規操作。
在C語言的教程中typedef用於取別名,形式下:
typedef 舊名字 新名字;
確實也是這樣,但遇到給函數指針類型、數組類型等定義別名的時候就要特別區分了。如:
typedef char ARRAY20[20]; ARRAY20 a1,a2; /* 等價於char a1[20],a2[20]; */ typedef void (*FunType)(int); /*這樣只是定義一個函數指針類型 */ FunType FunP; /*然後用FunType類型來聲明全局FunP變數*/
別問我為什麼,因為我也不知道。
當然,並不是說用到了函數指針就要用typedef定義一下,一般在結構體中使用函數指針就不會使用typedef,如下
typedef struct { uint8_t data; void (*FunP)(int); }Mode_Typedef;
以上均為個人建議,沒有優劣,大家根據自己的習慣做即可。
4、函數指針作為入參
既然函數指針變數是一個變數,當然也可以作為某個函數的參數來使用的。所以,你還應知道函數指針是如何作為某個函數的參數來傳遞使用的。
示例代碼如下:
#include <stdio.h> 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); }
運行結果如下
可以看到,CallMyFun函數的參數是一個指針,當這個函數指針有參數時,需要通過另外增加一個參數來保存回調函數的參數值,同理也可以使用多個參數的函數指針。
5、單片機IAP
在單片機OTA時常用到函數指針,代碼如下
typedef void (*IapFun)(void);//定義一個函數指針 IapFun Jump_To_Application;//定義函數指針對象 if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000)//檢查地址是否有效 { Jump_To_Application = (iapfun) * (__IO uint32_t *)(appxaddr + 4);//用戶代碼區第二個字為程式開始地址(複位地址) MSR_MSP(*(__IO uint32_t *)appxaddr);//初始化APP堆棧指針(用戶代碼區的第一個字用於存放棧頂地址) Jump_To_Application();//跳轉app }
這裡直接將地址強制轉換成函數指針,然後執行這個函數。appxaddr地址就是新固件存儲的起始地址,appxaddr+4的位置就是新固建中的Reset_Handler函數,相當於執行了新固件中的Reset_Handler。
點擊查看本文所在的專輯:C語言進階專輯