1. 程式人生 > 其它 >指標與函式

指標與函式

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語言進階專輯