我們一起聊聊指針與函數(shù)
指針函數(shù)
指針函數(shù),從名字上看它本質(zhì)上是一個函數(shù)。指針函數(shù):返回值類型是指針的函數(shù)。函數(shù)聲明如下:
int *plusfunction(int a,int b);
當(dāng)然也可以寫成如下格式:
int* plusfunction(int a,int b);
讓指針標(biāo)志 * 與int緊貼在一起,而與函數(shù)名f間隔開,這樣看起來就明了些了,plusfunction是函數(shù)名,返回值類型是一個int類型的指針。
指針函數(shù)就是一個普通的函數(shù),普通到僅僅是因為它的函數(shù)返回值是指針而已。
#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);
}
這是一個簡單的指針函數(shù)的例子,運行結(jié)果如下,本文代碼在VScode平臺運行,使用方法《使用VScode調(diào)試C語言》。
不過我有個疑問,使用指針函數(shù),和函數(shù)入?yún)⑹侵羔樣惺裁春锰幠???
#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;
}
這樣執(zhí)行也是沒問題的啊,當(dāng)然我也發(fā)現(xiàn)了指針函數(shù)的好處,就是可以把函數(shù)作為另一個函數(shù)的入?yún)ⅰ?/p>
testfunction(plusfunction(1,2));
在這點上用第二種方法,將指針作為函數(shù)入?yún)⑹遣恍械摹?/p>
還有,將指針作為函數(shù)入?yún)⑶靶枰蛑羔樕暾垉?nèi)存,而指針函數(shù)卻不用。
除去這兩點,日常開發(fā)中,我還真沒找到指針函數(shù)的“優(yōu)點”,讓我覺得某個功能必須用指針函數(shù)實現(xiàn),或用指針函數(shù)實現(xiàn)后代碼更整潔,提高代碼可讀性。
函數(shù)指針
函數(shù)指針,本質(zhì)上他是一個指針,并不是一個函數(shù)。在C語言中有些概念是一脈相承的,之前的推文《指針與數(shù)組》,數(shù)組指針和指針數(shù)組的概念更有效幫你理解函數(shù)指針和指針函數(shù)。函數(shù)指針說的就是一個指針,但這個指針指向的函數(shù),不是普通的基本數(shù)據(jù)類型或者類對象。函數(shù)指針定義如下:
int (*f)(int a,int b);//聲明函數(shù)指針
和指針函數(shù)的定義對比可以看到,函數(shù)指針與指針函數(shù)的最大區(qū)別是函數(shù)指針的函數(shù)名是一個指針,即函數(shù)名前面有一個指針類型的標(biāo)志型號“*”。注意指針函數(shù)與函數(shù)指針表示方法的不同,千萬不要混淆。最簡單的辨別方式就是看函數(shù)名前面的指針*號有沒有被括號()包含,如果被包含就是函數(shù)指針,反之則是指針函數(shù)。當(dāng)然,函數(shù)指針的返回值也可以是指針。簡單的函數(shù)調(diào)用示例:
#include <stdio.h>
void MyFun(int a);
int main()
{
MyFun(10);
return(0);
}
void MyFun(int a)
{
printf("a is %d\n",a);
}
這是一個再簡單不過的函數(shù)調(diào)用了,其實他還可以寫作下面格式:
#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);是一樣的,在這里強(qiáng)烈建議沒有看過《指針與數(shù)組》的同學(xué),先看一下。在教材和資料中,都會講到數(shù)組名就是指向數(shù)組第一個數(shù)據(jù)的常量指針。從上面例子看到,函數(shù)名貌似也是“常量指針”。數(shù)組中,可以將數(shù)組名賦給一個指針,然后通過指針訪問數(shù)組中的內(nèi)容,那么我們就可以定義一個函數(shù)指針,將函數(shù)名賦給函數(shù)指針,通過這個函數(shù)指針調(diào)用函數(shù)。
#include <stdio.h>
void MyFun(int a);/* 這個聲明也可寫成:void MyFun( int )*/
void (*FunP)(int);/*也可聲明成void(*FunP)(int x),但習(xí)慣上一般不這樣。 */
int main()
{
FunP = MyFun;
*FunP(10);
return(0);
}
void MyFun(int a)
{
printf("a is %d\n",a);
}
在第7行在函數(shù)指針前加*相當(dāng)取指針的值,在這里理解為將MyFun函數(shù)取出。那么再進(jìn)一步:
#include <stdio.h>
void MyFun(int a); /* 這個聲明也可寫成:void MyFun( int )*/
void (*FunP)(int); /*也可聲明成void(*FunP)(int x),但習(xí)慣上一般不這樣。 */
int main()
{
FunP = MyFun;
FunP(10);
return (0);
}
void MyFun(int a)
{
printf("a is %d\n", a);
}
是的,將FunP前面的*號拿掉也是可以運行的,上面的示例代碼就是函數(shù)指針在C語言中的最常見形態(tài)。之前的例子只是為了讓你更能理解函數(shù)指針,實際開發(fā)中只需要用函數(shù)指針的最終,最常見的形態(tài)即可。
不然代碼中出現(xiàn)之前的形式,其他程序員并不是很熟悉,就成了“騷操作”,雖然不影響運行,但是降低代碼的可閱讀性。
typedef的引入
C語言中typedef關(guān)鍵字作用:復(fù)雜的聲明定義簡單的別名,很明顯我們上面講述的函數(shù)指針就是一個比較復(fù)雜的類型,可以使用typedef關(guān)鍵字將函數(shù)指針的定義簡單化。
#include <stdio.h>
void MyFun(int a); /* 這個聲明也可寫成:void MyFun( int )*/
typedef void (*FunType)(int); /*這樣只是定義一個函數(shù)指針類型 */
FunType FunP; /*然后用FunType類型來聲明全局FunP變量*/
int main()
{
FunP = MyFun;
FunP(10);
return (0);
}
void MyFun(int a)
{
printf("a is %d\n", a);
}
強(qiáng)烈建議使用typedef和函數(shù)指針組合的方式,這是最常見的方式,大家都能看懂的常規(guī)操作。在C語言的教程中typedef用于取別名,形式下:
typedef 舊名字 新名字;
確實也是這樣,但遇到給函數(shù)指針類型、數(shù)組類型等定義別名的時候就要特別區(qū)分了。如:
typedef char ARRAY20[20];
ARRAY20 a1,a2; /* 等價于char a1[20],a2[20]; */
typedef void (*FunType)(int); /*這樣只是定義一個函數(shù)指針類型 */
FunType FunP; /*然后用FunType類型來聲明全局FunP變量*/
別問我為什么,因為我也不知道。
當(dāng)然,并不是說用到了函數(shù)指針就要用typedef定義一下,一般在結(jié)構(gòu)體中使用函數(shù)指針就不會使用typedef,如下:
typedef struct
{
uint8_t data;
void (*FunP)(int);
}Mode_Typedef;
以上均為個人建議,沒有優(yōu)劣,大家根據(jù)自己的習(xí)慣做即可。
函數(shù)指針作為入?yún)?/h4>
既然函數(shù)指針變量是一個變量,當(dāng)然也可以作為某個函數(shù)的參數(shù)來使用的。所以,你還應(yīng)知道函數(shù)指針是如何作為某個函數(shù)的參數(shù)來傳遞使用的。示例代碼如下:
#include <stdio.h>
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int); /* ②. 定義一個函數(shù)指針類型FunType,與①函數(shù)類型一致 */
void CallMyFun(FunType fp, int x);
int main(int argc, char *argv[])
{
CallMyFun(MyFun1, 10); /* ⑤. 通過CallMyFun函數(shù)分別調(diào)用三個不同的函數(shù) */
CallMyFun(MyFun2, 20);
CallMyFun(MyFun3, 30);
}
void CallMyFun(FunType fp, int x) /* ③. 參數(shù)fp的類型是FunType。*/
{
fp(x); /* ④. 通過fp的指針執(zhí)行傳遞進(jìn)來的函數(shù),注意fp所指的函數(shù)是有一個參數(shù)的。 */
}
void MyFun1(int x) /* ①. 這是個有一個參數(shù)的函數(shù),以下兩個函數(shù)也相同。 */
{
printf("MyFun1:%d\n", x);
}
void MyFun2(int x)
{
printf("MyFun2:%d\n", x);
}
void MyFun3(int x)
{
printf("MyFun3:%d\n", x);
}
運行結(jié)果如下:
可以看到,CallMyFun函數(shù)的參數(shù)是一個指針,當(dāng)這個函數(shù)指針有參數(shù)時,需要通過另外增加一個參數(shù)來保存回調(diào)函數(shù)的參數(shù)值,同理也可以使用多個參數(shù)的函數(shù)指針。
單片機(jī)IAP
在單片機(jī)OTA時常用到函數(shù)指針,代碼如下:
typedef void (*IapFun)(void);//定義一個函數(shù)指針
IapFun Jump_To_Application;//定義函數(shù)指針對象
if (((*(__IO uint32_t*)appxaddr) & 0x2FFE0000 ) == 0x20000000)//檢查地址是否有效
{
Jump_To_Application = (iapfun) * (__IO uint32_t *)(appxaddr + 4);//用戶代碼區(qū)第二個字為程序開始地址(復(fù)位地址)
MSR_MSP(*(__IO uint32_t *)appxaddr);//初始化APP堆棧指針(用戶代碼區(qū)的第一個字用于存放棧頂?shù)刂?
Jump_To_Application();//跳轉(zhuǎn)app
}
這里直接將地址強(qiáng)制轉(zhuǎn)換成函數(shù)指針,然后執(zhí)行這個函數(shù)。appxaddr地址就是新固件存儲的起始地址,appxaddr+4的位置就是新固建中的Reset_Handler函數(shù),相當(dāng)于執(zhí)行了新固件中的Reset_Handler。