掌握指針進階:一篇帶你玩轉函數指針、函數指針數組及指向函數指針數組的指針!!

🍁博客主頁:江池俊的博客

💫收錄專欄:C語言進階之路

💡代碼倉庫:江池俊的代碼倉庫

🎪我的社區:GeekHub

🎉歡迎大家點贊👍評論📝收藏?

在這里插入圖片描述

文章目錄

  • 一、函數指針
      • 代碼1:
      • 代碼2:
  • 二、函數指針數組
    • 什么是函數指針數組?
    • 為什么使用函數指針數組?
    • 函數指針數組的基本用法
  • 三、 指向函數指針數組的指針
    • 指向函數指針數組的指針是什么?
    • 為什么使用指向函數指針數組的指針?
  • 總結


一、函數指針

在C語言中,函數是一等公民,可以像其他變量一樣被傳遞和使用。而函數指針就是指向函數的指針變量,可以用來調用函數。本文將介紹函數指針的定義、使用方法以及注意事項。

函數指針的定義格式為:

返回值類型 (*指針變量名)(參數列表);

其中,返回值類型表示函數的返回值類型,指針變量名是指向函數的指針變量的名稱,參數列表表示函數的參數類型和數量。

例如,定義一個指向返回值為int、參數為兩個int類型的函數的指針:

int (*pAdd)(int, int);

這個指針可以指向任何返回值為int、參數為兩個int類型的函數。

先看一段代碼:

#include <stdio.h>
void test()
{printf("hehe\n");
}int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}

輸出結果:
在這里插入圖片描述
輸出的是兩個地址,這兩個地址是 test 函數的地址。
那我們的函數的地址要想保存起來,怎么保存?
下面我們看代碼:

void test()
{printf("hehe\n");
}
//下面pfun1和pfun2哪個有能力存放test函數的地址?
void (*pfun1)();
void *pfun2();

首先,能給存儲地址,就要求pfun1或者pfun2是指針,那哪個是指針?
答案是:

pfun1可以存放。pfun1先和*結合,說明pfun1是指針,指針指向的是一個函數,指向的函數無參數,返回值類型為void。

閱讀兩段有趣的代碼:

//代碼1
(*(void (*)())0)();
//代碼2
void (*signal(int , void(*)(int)))(int);

這兩段代碼都涉及了函數指針的用法,讓我們逐一來解釋它們:

代碼1:

(*(void (*)())0)();

這段代碼是一個函數指針調用的例子。讓我們逐步分解它:

  • void (*)() 表示一個函數指針類型,它指向一個不接受任何參數(void),并且返回類型為 void 的函數。

  • (void (*)())0這里的(void (*)())實際上是一個強制轉化的操作,它是將0強制轉化為函數指針類型,即是將函數指針初始化為一個地址為 0 的空指針,也就是一個無效的函數指針。

  • (*(void (*)())0)(); 則是將這個無效的函數指針進行了間接調用,實際上是試圖調用地址為 0 的函數,這通常會導致程序崩潰(因為操作系統不允許在地址 0 處執行代碼,會觸發段錯誤)。


這段代碼在 C 語言中屬于未定義行為,不應該在實際代碼中使用,因為它可能導致程序的崩潰或其他不可預測的行為。

代碼2:

void (*signal(int, void(*)(int)))(int);
//我們也可以將它簡化為以下這種形式:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

這段代碼涉及的是 C 語言中的信號處理函數 signal 的聲明。讓我們逐步解釋它:

  • void(*)(int) 表示一個函數指針類型,它指向一個接受一個 int 參數并返回 void 的函數。

  • signal 是一個函數,它接受兩個參數:一個 int 參數和一個函數指針參數,然后返回一個與上述函數指針類型匹配的函數指針。


所以整個代碼聲明的含義是:signal 是一個函數,它接受一個 int參數和一個函數指針參數,返回一個函數指針,該函數指針指向一個接受一個 int 參數并返回 void 的函數,這個函數通常用于處理信號。


這段代碼通常用于在 C 語言中設置信號處理函數,以便在程序接收到特定信號時執行特定的操作。


請注意,這里只是聲明了 signal 函數的原型,實際使用時需要根據具體情況編寫函數體。

:推薦《C陷阱和缺陷》

這本書中提及這兩個代碼。


二、函數指針數組

函數指針數組是 C 語言中一個強大且常用的工具,用于存儲指向不同函數的指針,允許根據需要調用特定的函數。在本文中,我們將深入介紹函數指針數組的概念、用途和實例,幫助你理解并充分利用這一重要的 C 語言特性。

什么是函數指針數組?

數組是一個存放相同類型數據的存儲空間,那我們已經學習了指針數組
比如:

int *arr[10];
//數組的每個元素是int*

那要把函數的地址存到一個數組中,那這個數組就叫函數指針數組,那函數指針的數組如何定義呢?

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1
parr1 先和 [] 結合,說明 parr1是數組,數組的內容是什么呢?
int (*)() 類型的函數指針。
函數指針數組的用途:轉移表

總之,函數指針數組實際上是一個數組,其元素都是指向函數的指針。這使得我們可以將不同的函數存儲在數組中,并通過索引來調用特定的函數。這種靈活性使得函數指針數組在編寫菜單驅動程序、狀態機、回調機制等方面非常有用。以下是一個簡單的示例:

#include <stdio.h>void func1() 
{printf("調用 func1函數\n");
}void func2() 
{printf("調用 func2函數\n");
}int main() 
{void (*funcPtrArray[2])() = {func1, func2};//函數指針數組funcPtrArrayfuncPtrArray[0](); //通過函數指針數組調用 func1funcPtrArray[1](); //通過函數指針數組調用 func2return 0;
}

為什么使用函數指針數組?

函數指針數組在以下情況下非常有用:

  1. 菜單驅動程序: 當需要實現一個用戶界面,允許用戶從菜單中選擇不同的操作時,函數指針數組可以用來存儲每個操作的處理函數。

  2. 狀態機: 在狀態機的實現中,可以使用函數指針數組來存儲每個狀態的處理函數,從而實現狀態轉換時的操作。

  3. 回調機制: 當你需要在某個事件發生時調用不同的函數,比如事件處理、信號處理等,函數指針數組提供了一種簡潔的方式。

  4. 動態選擇算法: 如果你有多個算法實現,但在運行時決定使用哪一個算法,函數指針數組可以幫助你實現動態選擇算法。

函數指針數組的基本用法

讓我們通過一個簡單的例子來演示函數指針數組的基本用法:實現一個簡單的計算器,允許用戶選擇不同的操作。
例子:(計算機)

#include <stdio.h>int add(int a, int b)
{return a + b;
}int subtract(int a, int b)
{return a - b;
}int multiply(int a, int b)
{return a * b;
}int division(int a, int b)
{return a / b;
}int main()
{int x, y;int input = 1;int restult = 0;// 定義函數指針數組,存儲不同的操作函數//轉移表int(*operation[])(int x, int y) = { 0,add,subtract,multiply,division };while (input){printf("\n*************************\n");printf(" 1:add           2:subtract \n");printf(" 3:multiply      4:division \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("輸入操作數:");scanf("%d %d", &x, &y);restult = (*operation[input])(x, y);// 調用選定的函數printf("restult = %d\n", restult);}elseprintf("輸入有誤,請重新輸入\n");}return 0;
}

在上述示例中,我們定義了一個函數指針數組 operation,其中的元素分別指向 addsubtractmultiplydivision 函數。用戶可以根據選擇來執行不同的操作。


三、 指向函數指針數組的指針

指向函數指針數組的指針是什么?

指向函數指針數組的指針是一個指針,指針指向一個的數組,數組元素都是函數指針。這種指針提供了對函數指針數組的更高級別的訪問方式,使我們能夠更靈活地處理函數指針數組。以下是一個示例:

#include <stdio.h>void func1() 
{printf("調用 func1函數\n");
}void func2() 
{printf("調用 func2函數\n");
}int main() 
{void (*funcPtrArray[2])() = {func1, func2};//函數指針的數組funcPtrArrayvoid (*(*ptrToFuncPtrArray))() = funcPtrArray;//指向函數指針數組funcPtrArray的指針ptrToFuncPtrArrayptrToFuncPtrArray[0](); // 通過指向函數指針數組的指針調用 func1ptrToFuncPtrArray[1](); // 通過指向函數指針數組的指針調用 func2return 0;
}

為什么使用指向函數指針數組的指針?

指向函數指針數組的指針可能在日常編程中不常見,但在某些情況下非常有用。以下是一些使用情況:

  1. 函數指針數組的參數傳遞: 通過傳遞指向函數指針數組的指針作為參數,可以避免復制整個數組,從而提高效率。

  2. 動態函數調用: 使用指向函數指針數組的指針,可以在運行時根據條件選擇不同的函數進行調用。

  3. 代碼模塊化: 當函數指針數組較大或需要在多個函數之間共享時,使用指向函數指針數組的指針可以提高代碼的模塊化性。

  4. 函數指針數組的排序: 可以使用指向函數指針數組的指針來執行對函數指針數組的排序操作,以實現按照某種規則調用函數。

總結

  • 在本篇博客中,我們深入探討了 C語言中的三個重要概念:函數指針、函數指針數組和指向函數指針數組的指針。這些概念雖然可能聽起來有些復雜,但它們為我們在C編程中提供了更大的靈活性和功能。
  • 函數指針允許我們將函數作為數據,傳遞給其他函數或存儲在數據結構中。通過使用函數指針,我們可以實現更動態和可配置的程序設計,同時避免代碼的重復。
  • 函數指針數組進一步擴展了這種靈活性,允許我們將多個函數指針組織在一個數組中,以便在運行時根據需要選擇和調用不同的函數。這在構建可插拔的模塊和實現動態行為時特別有用。
  • 最后,我們介紹了指向函數指針數組的指針,這為我們提供了一種更高級的訪問方式,使得處理函數指針數組變得更加優雅。它可以應用于參數傳遞、動態函數調用、代碼模塊化以及對函數指針數組的排序等各種場景,從而增強了程序的模塊性、可維護性和性能。

🔥今天的分享就到這里, 如果覺得博主的文章還不錯的話, 請👍三連支持一下博主哦🤞

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41854.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于Servlet實現的管理系統(包含服務器源碼+數據庫)

資料下載鏈接 介紹 基于Servlet框架的管理系統 簡潔版 &#xff1b; 實現 登錄 、 注冊 、 增 、 刪 、 改 、 查 &#xff1b; 可繼續完善增加前端、校驗、其他功能等&#xff1b; 可作為 Servlet項目 開發練習基礎模型&#xff1b; 課程設計 、 畢業設計 開發基礎&…

JVM---jvm里的內存溢出

目錄 堆溢出 虛擬機棧和本地方法棧溢出&#xff08;棧溢出很少出現&#xff09; 方法區和運行時常量池溢出 本機內存直接溢出&#xff08;實際中很少出現、了解即可&#xff09; 堆溢出 堆溢出&#xff1a;最常見的是大list&#xff0c;list里面有很多元素 堆溢出該怎么解決…

第7章:貝葉斯分類器

貝葉斯決策論 貝葉斯分類器&#xff1a;使用貝葉斯公式 貝葉斯學習&#xff1a;使用分布估計&#xff08;不同于頻率主義的點估計&#xff09; 極大似然估計 樸素貝葉斯分類 半樸素貝葉斯 條件獨立性假設&#xff0c;在現實生活中往往很難成立。 半樸素貝葉 斯的一個常用策略…

C++學習筆記4

什么是指針&#xff1f; 指針是存儲內存地址的變量。就像int變量用于存儲整數值一樣&#xff0c;指針變量用于存儲內存地址。指針是一種指向內存單元的特殊變量。 內存單元地址通常使用的是16進制表示&#xff08;0&#xff5e;9和A&#xff5e;F&#xff09;來表示數字。顯示…

React源碼解析18(6)------ 實現useState

摘要 在上一篇文章中&#xff0c;我們已經實現了函數組件。同時可以正常通過render進行渲染。 而通過之前的文章&#xff0c;beginWork和completeWork也已經有了基本的架子。現在我們可以去實現useState了。 實現之前&#xff0c;我們要先修改一下我們的index.js文件&#x…

DAY2,ARM(特殊功能寄存器,數據操作指令,跳轉指令)

1.cmp、sub、b指令的使用&#xff1b; 代碼&#xff1a; .text .global _start _start:mov r0,#9mov r1,#15loop:cmp r0,r1beq stopsubcc r1,r1,r0subhi r0,r0,r1b loopstop:b stop .end結果&#xff1a; 2.匯編指令計算1~100之間和&#xff1b; 代碼&#xff1a; .text .gl…

【從零學習python 】47. 面向對象編程中的繼承概念及基本使用

文章目錄 繼承的基本使用代碼逐行講解說明:進階案例 繼承的基本使用 在現實生活中&#xff0c;繼承一般指的是子女繼承父輩的財產&#xff0c;父輩有的財產&#xff0c;子女能夠直接使用。 程序里的繼承 繼承是面向對象軟件設計中的一個概念&#xff0c;與多態、封裝共為面向對…

Android 13 Launcher——屏蔽上拉到應用列表

背景 Launcher定制需要將原先的應用列表去掉,可以從根源去掉,就是將上拉出現應用列表的上拉手勢直接屏蔽,讓其不能上拉出現應用列表界面,在研究的過程中順便將下拉出現負一屏的邏輯也研究了下,如下就是具體實現。 目錄 背景 一.如何屏蔽上拉出現應用列表 一.如何屏蔽上拉…

培訓報名小程序-用戶注冊

目錄 1 創建數據源2 注冊用戶3 判斷用戶是否注冊4 完整代碼總結 我們的培訓報名小程序&#xff0c;用戶每次打開時都需要填寫個人信息才可以報名&#xff0c;如果用戶多次報名課程&#xff0c;每次都需要填寫個人信息&#xff0c;比較麻煩。 本篇我們就優化一下功能&#xff0c…

線上售樓vr全景看房成為企業數字化營銷工具

在房地產業中&#xff0c;VR全景拍攝為買家提供了虛擬看房的全新體驗。買家可以通過相關設備&#xff0c;遠程參觀各個樓盤的樣板間和實景&#xff0c;感受房屋的空間布局和環境氛圍&#xff0c;極大地提高了購房決策的準確性。對于房地產開發商和中介機構來說&#xff0c;VR全…

@Async用哪個線程池

一共可以分三種情況 第一種 未在手動在項目中配置任何線程池 spring boot 會默認添加一個coreSize8的 無界線程池&#xff0c;名稱為applicationTaskExecutor &#xff08;源碼&#xff1a;org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration&…

如何搭建個人郵件服務hmailserver并實現遠程發送郵件

文章目錄 1. 安裝hMailServer2. 設置hMailServer3. 客戶端安裝添加賬號4. 測試發送郵件5. 安裝cpolar6. 創建公網地址7. 測試遠程發送郵件8. 固定連接公網地址9. 測試固定遠程地址發送郵件 hMailServer 是一個郵件服務器,通過它我們可以搭建自己的郵件服務,通過cpolar內網映射工…

計算機競賽 GRU的 電影評論情感分析 - python 深度學習 情感分類

1 前言 &#x1f525;學長分享優質競賽項目&#xff0c;今天要分享的是 &#x1f6a9; GRU的 電影評論情感分析 - python 深度學習 情感分類 &#x1f947;學長這里給一個題目綜合評分(每項滿分5分) 難度系數&#xff1a;3分工作量&#xff1a;3分創新點&#xff1a;4分 這…

代碼隨想錄算法訓練營第三十八天 | 理論基礎,509. 斐波那契數,70. 爬樓梯,746. 使用最小花費爬樓梯

代碼隨想錄算法訓練營第三十八天 | 理論基礎&#xff0c;509. 斐波那契數&#xff0c;70. 爬樓梯&#xff0c;746. 使用最小花費爬樓梯 理論基礎什么是動態規劃動態規劃的解題步驟動態規劃應該如何debug 509. 斐波那契數遞歸解法 70. 爬樓梯746. 使用最小花費爬樓梯 理論基礎 視…

計蒜客T1170——人民幣支付

超級水&#xff0c;不解釋&#xff0c;代碼的處理方式減低了繁瑣程度&#xff0c; #include <iostream> using namespace std;int main(int argc, char** argv) {int num0;cin>>num;int money[6]{100,50,20,10,5,1};for(int i0;i<5;i){int count0;countnum/mone…

SkyWalking 部署(包含ES)

SkyWalking安裝 結構 首先SkyWalking主要需要oapService、webApp、Elasticsearch&#xff08;可選存儲&#xff09;三個&#xff0c;接下來講一下這三個的安裝步驟&#xff0c;安裝過程中出現了一些細小的配置錯誤&#xff0c;導致用了快兩天才弄好&#xff0c;麻木了&#x…

C++超基礎語法

&#x1f493;博主個人主頁:不是笨小孩&#x1f440; ?專欄分類:數據結構與算法&#x1f440; C&#x1f440; 刷題專欄&#x1f440; C語言&#x1f440; &#x1f69a;代碼倉庫:笨小孩的代碼庫&#x1f440; ?社區&#xff1a;不是笨小孩&#x1f440; &#x1f339;歡迎大…

IDEA常用工具配置

IDEA常用工具&配置 如果發現插件市場用不了&#xff0c;可以設置Http Proxy&#xff0c;在該界面上點擊”Check connection“并輸入的地址&#xff1a;https://plugins.jetbrains.com/ 。 一、常用插件 1、MybatisX Mybaits Plus插件&#xff0c;支持java與xml互轉 2、F…

Vue-10.集成.env

.env、.env.development 和 .env.preview .env、.env.development 和 .env.preview 文件是用于配置環境變量和應用程序設置的文件&#xff0c;它們在項目開發和部署過程中起到關鍵作用。這些文件用于在不同的環境中設置不同的變量值&#xff0c;以滿足不同環境下的配置需求。 …

日志系統——日志格式化模塊設計

一&#xff0c;模塊主要成員 該模塊的主要作用是對日志消息進行格式化&#xff0c;將日志消息組織成制定格式的字符串。 該模塊主要成員有兩個&#xff1a;1.格式化字符串。 2.格式化子項數組 1.1 格式化字符串 格式化字符串的主要功能是保存日志輸出的格式字符串。其格式化字…