指針與函數

来源:https://www.cnblogs.com/Fireflycjd/archive/2022/04/10/16124747.html
-Advertisement-
Play Games

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 執行周期 1. SetParametersAsync 2. OnInitializedAsync(調用兩次) 和 OnInitialized; 3. OnParametersSetAsync 或 OnParametersSet; 4. OnAfterRenderAsync 和 OnAfterRend ...
  • 熟悉C#開發的朋友們都應該知道,使用C#語言編寫的程式在編譯之後生成的文件被稱做為程式集,這其中又分為dll(類庫)和exe(可執行程式)兩種類型,而程式集當中的內容其實就CIL(Common Intermediate Language,公共中間語言)。CIL最初是隨著.NET由微軟一起發佈的,因此 ...
  • 大家好,好久沒有輸出博文了,一是因為比較忙,另外一個原因是最近主要的精力是在給 AgileConfig 添加一個新的功能:服務註冊與發現。 先說說為什麼會添加這個功能。我自己的項目是用 Consul 來做為服務註冊發現組件的。自從我上線了 AgileConfig 做為配置中心後,我就很少去 Cons ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 Docker概述 Docker學習鏈接 官網鏈接:Home - Docker Docker與虛擬機比較 虛擬化技術 容器技術不是模擬的一個完整的操作系統。 比較Docker 和虛擬機技術不同 傳統虛擬機,虛擬出一臺硬體,運行完整的操作系統。 容器 ...
  • 首先,先上結構圖,請對照代碼理解。 (一)什麼是任務切換? 任務切換就是在就緒列表裡面尋找優先順序最高的就緒任務,然後執行該任務。 (二)任務什麼時候切換? 1)、當執行系統調用的時候,進行任務切換。 2)、當發生滴答定時器(systick)中斷的時候,進行任務切換。 情況1:執行系統調用時 所謂的系 ...
  • 鏡像下載、功能變數名稱解析、時間同步請點擊 阿裡雲開源鏡像站 一、環境準備 1、鏡像包 CentOS-7.9-x86_64-DVD-2009.iso ubuntu-18.04.6-server-amd64.iso 2、VMware 二、鏡像下載 阿裡雲:developer.aliyun.com/mirror ...
  • 解決過程 在使用stm32H743+外置USB2.0高速phy(smsc USB3343)過程中,發現設備無法被枚舉為hs模式,而是一直被枚舉為fs。測試速度,如下: 16:24:24.672288:開始測試單片機向上位機發送數據…… 16:24:25.671740:結束測試,速度約為 831.48 ...
  • 在我們日常工作中,為了驗證開發的功能,比如:文件上傳功能或者演算法的處理效率等,經常需要一些大文件進行測試,有時在四處找了一頓之後,發現竟然沒有一個合適的,雖然 Linux 中也有一些命令比如:vim、touch 等可以創建文件,但是如果需要一個 100G 或者 1T 的大文件,這些命令就顯得力不從心 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...