C 語言學習筆記(指針4)

內容提要

  • 指針
    • 函數指針與指針函數
    • 二級指針

指針

函數指針與指針函數

函數指針
定義

函數指針本質上是指針,是一個指向函數的指針。函數都有一個入口地址,所謂指向函數的指針,就是指向函數的入口地址。(這里的函數名就代表入口地址

函數指針存在的意義:

  • 讓函數多了一種調用方式
  • 函數指針可以作為形式,可以形式調用(回調函數)

語法

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

舉例

int (*p)(int a, int b);
函數指針的初始化

①定義的同時賦值

// 函數指針需要依賴于函數,先有函數,后有指針// 定義一個普通函數
// 普通函數
int add(int a, int b){return a + b};// 定義一個函數指針,并初始化
// 觀察,函數指針的返回類型和指向函數的返回類型一致,函數的形參列表個數、類型、順序跟指向函數的形參列表一致
int (*p)(int a, int b) = add; // 函數指針p指向函數add,這里的add不能帶(),add就是該函數的入口地址

在這里插入圖片描述

②先定義后賦值

// 函數指針需要依賴于函數,先有函數,后有指針// 定義一個普通函數
// 普通函數
int add(int a, int b){return a + b};// 定義一個函數指針,并初始化
// 觀察,函數指針的返回類型和指向函數的返回類型一致,函數的形參列表個數、類型、順序跟指向函數的形參列表一致
int (*p)(int, int) = add; //形參列表的參數名可以省略p = add; //此時是將add的入口地址賦值給指針

注意:
1.函數指針指向的函數要和函數指針定義的返回值類型,形參列表對應,否則編譯報錯

2.函數指針是指針,但不能指針運算,如p++等,沒有實際意義

3.函數指針作為形參,可以形成回調

4.函數指針作為形參,函數調用時的實參只能是與之對應的函數名,不能帶小括號()

5.函數指針的形參列表中的變量名可以省略

注意:函數不能作為函數的形參,但是指向函數的函數指針是可以作為函數的形參的。

案例
  • 需求:求a,b兩個數的最大值
  • 代碼:
/**
* 定義一個函數,求兩個數的最大值
**/
int ger_max(int a, int b)
{return  a > b ? a : b;
}int main()
{// 定義測試數據int a = 3, b = 4, max;// 直接調用函數max = get_max(a,b);printf("%d,%d中的最大值是%d\n",a,b,max);// 定義一個函數指針int (*p)(int,int) = get_max;// 間接調用函數:方式1max = p(a,b); //直接將指針名作為函數名printf("%d,%d中的最大值是%d\n",a,b,max);// 間接調用函數:方式2max = (*p)(a,b); //直接將指針名作為函數名printf("%d,%d中的最大值是%d\n",a,b,max);return 0;
}
回調函數

定義

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針作為參數傳遞給另一個函數,當這個指針備用來調用其所指向的函數時。我們就說這是回調函數。回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應。

為什么要用回調函數

因為可以把調用者與被調用者分開,所以調用者不關心誰是被調用者。它只需知道存在一個具有特定原型和限制條件的被調用函數。

簡而言之,回調函數就是允許用戶把需要調用的方法的指針作為參數傳遞給一個函數,以便該函數在處理相似事件的時候可以靈活的使用不同的方法。

/**
*回調函數1
*/
int callback_1(int a)
{printf("hello, this is callback 1:a=%d\n", a),return a;
}/**
*回調函數2
*/
int callback_2(int b)
{printf("hello, this is callback_2:b=%d\n", b);return b;
}/**
*實現回調函數(函數的參數是函數指針)
*/
int handle(int x, int(*callback)(int))
{printf("日志:開始執行任務!\n");int res = callback(x);printf("日志:執行結果:%d\n", res);printf("日志:結束執行任務!\n");
}int main(int argc,char *argv[])
{handle(100,callback_1);handle(200,callback_2);    return 0;
}

在這里插入圖片描述

指針函數
定義

本質上是函數,這個函數的返回值類型是指針,整個函數稱之為指針函數。

  • int *p:普通指針。
  • int (*p)[3]:數組指針。
  • int *p[]:指針數組。
  • int (*p)():函數指針。
  • int *p():指針函數。
語法:
// 寫法1
數據類型* 函數名(形參列表)
{函數體;return 指針變量;
}
// 寫法2
數據類型 *函數名(形參列表)
{函數體;return 指針變量;        
}
舉例:
int *get(int a)
{int *p = &a;return 0;
}
int main()
{int *a= get(5);printf("%d\n"*a);
}
注意:

在函數中不要直接返回一個局部變量的地址,因為函數調用完畢后,局部變量會被回收,使得返回的地址就不明確,此時返回的指針就是野指針。

解決方案

如果非要訪問,可以給這個局部變量添加(定義的時候添加)static ,可以延長它的生命周期,從而避免野指針(盡量少用,因為存在內存泄漏)

演示案例

int *add(int a, int b)
{static int sum; // 使用static修飾局部變量,會提升生命周期,但是作用域不會發生改變,不建議sum = a + b;return ∑ // 執行完return 作為函數作用域的布局變量sum的空間被釋放
}int main()
{int *res = add(5,3); // 接收到了地址,但是地址對應的空間已經被釋放printf("%d\n", *res);return 0;        
}
案例
  • 需求:有若干個學生,每個學生有4門成績,要求在用戶輸入學號(int id)后,能輸出該學生的全部成績(float scores[4]),用指針函數實現。
/**
* 定義一個函數,要求輸入學生序號,返回該學生所有成績
* @param all:所有人的成績
* @param id:要檢索學生的序號
* @return id:對應血行的成績數組(指針)
*/
float* search(float (*all)[4], int id)
{// 定義一個指針變量,用來接受查詢到的某個學生的所有成績float *pt;pt = *(all + id); //行偏移return pt; // 賦值運算中 float pt[4] == float *pt;
}int main()
{// 準備一個二維數組,存儲3個學生的成績float scores[][4] = {{60,70,80,90},{66,77,88,99},{61,71,81,91}};// 定義一個變量,用來接收學生序號int m;printf("請輸入學生序號(0~2):\n");scanf("%d", &m);printf("第%d個學生的成績:\n", m);// 創建一個指針,用來接收成績float *p = search(scores, m);// 遍歷成績for(; p < scores[m] + 4; p++){printf("%5.2f\t", *p);}printf("\n");return 0;
}

二級指針

定義

二級指針(多重指針)用于存儲一級指針的地址,需要兩次解引用才能訪問原始數據,其他多級指針的用法類似,實際開發中最常見的多級指針是二級指針

int a = 10; // a是普通變量
int *p = &a; // 一級指針(p指向a,p存儲的是a的地址)
int **w = &p; // 二級指針(w指向p,w存儲的是p的地址)
int ***x = &w // 三級指針(x指向w,x存儲的是w的地址)
語法
數據類型 **指針變量名 = 指針數組的數組名 | 一級指針的地址
特點

與指針數組的等效性二級指針與指針數組等效性,但與二維數組不等效。二維數組名是數組指針類型,如int (*)[3],而非二級指針。

// 指針數組
int arr[] = {11,22,33};
int* arr_[] = {&arr[0], &arr[1], &arr[2]}; // 正確的指針數組的定義// 二級指針接受指針數組
char* str[3] = {"abc","aaa034","12a12"}; // str存儲的是三個字符串的首地址
char **p = str;

與二維數組的差異二維數組名是數組指針類型,直接賦值給二級指針會導致類型不匹配

int arr[2][3] = {{1,3,5}{11,33,55}};
int (*p)[3] = arr; //arr這個數組名就是數組指針類型int **k = arr; // 編譯報錯,arr類型 int(*)[3] 不兼容 k類型 int**
解引用

字符型二級指針可直接遍歷字符串數組,類似一維數組

void fun1()
{// 定義一個字符類型的指針數組char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i <len; i++){printf("%s\n",arr[i]);}printf("\n");
}void fun2()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二級指針等效于指針數組char **p = arr;for(int i = 0; i < len; i++){printf("%s\n",p[i]); //下標法printf("%s\n",*(p+i)); //指針法}printf("\n");
}void fun3()
{char *arr[] = {"orange","apple","banana","kiwi"};int len = sizeof(arr) / sizeof(arr[0]);// 二級指針等效于指針數組char **p;// 定義循環變量int i = 0// 遍歷指針數組do{p = arr + i; // p = arr ----> p = arr + 0printf("%s\n", *p);i++;}while(i < len);
}int main()
{fun1();fun2();fun3();return 0;
}

其他類型的二級指針需要兩次解引用訪問數據,常用于操作指針數組

int main()
{// 普通的一維數組int arr1[] = {11,22,33,44,55,66};// 創建一個指針數組int *arr[] = {&arr1[0],&arr1[1],&arr1[2],&arr2[3],&arr1[4],&arr1[5]};// 用一個二級指針接收指針數組int **p = arr;// 遍歷數組for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%-6d", *p[i]); //下標法printf("%-6d", **(p + i)); // 指針法 等價于*(*(p+i))}pritnf("\n");return 0;
}
總結

①二級制指針與指針數組等效,可簡化指針數組的遍歷操作

②二維數組名是數組指針類型(如: int(*)[3]),與二級指針( int**)類型不兼容。

③操作非字符型二級指針時,須通過兩次解引用訪問實際數據。

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

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

相關文章

C#串口打印機:控制類開發與實戰

C#串口打印機&#xff1a;控制類開發與實戰 一、引言 在嵌入式設備、POS 終端、工業控制等場景中&#xff0c;串口打印機因其穩定的通信性能和廣泛的兼容性&#xff0c;仍是重要的數據輸出設備。本文基于 C# 語言&#xff0c;深度解析一個完整的串口打印機控制類Printer&…

通過vue-pdf和print-js實現PDF和圖片在線預覽

npm install vue-pdf npm install print-js <template><div><!-- PDF 預覽模態框 --><a-modal:visible"showDialog":footer"null"cancel"handleCancel":width"800":maskClosable"true":keyboard"…

SQL解析工具JSQLParser

目錄 一、引言二、JSQLParser常見類2.1 Class Diagram2.2 Statement2.3 Expression2.4 Select2.5 Update2.6 Delete2.7 Insert2.8 PlainSelect2.9 SetOperationList2.10 ParenthesedSelect2.11 FromItem2.12 Table2.13 ParenthesedFromItem2.14 SelectItem2.15 BinaryExpressio…

安裝完dockers后就無法聯網了,執行sudo nmcli con up Company-WiFi,一直在加載中

Docker服務狀態檢查 執行 systemctl status docker 確認服務是否正常 若未運行&#xff0c;使用 sudo systemctl start docker && sudo systemctl enable docker 網絡配置沖突 Docker會創建docker0虛擬網橋&#xff0c;可能與宿主機網絡沖突 檢查路由表 ip route sho…

Docker 運維管理

Docker 運維管理 一、Swarm集群管理1.1 Swarm的核心概念1.1.1 集群1.1.2 節點1.1.3 服務和任務1.1.4 負載均衡 1.2 Swarm安裝準備工作創建集群添加工作節點到集群發布服務到集群擴展一個或多個服務從集群中刪除服務ssh免密登錄 二、Docker Compose與 Swarm 一起使用 Compose 三…

軟媒魔方——一款集合多種系統輔助組件的軟件

停更4年&#xff0c;但依舊吊炸天&#xff01; 親們&#xff0c;是不是覺得電腦用久了就像老牛拉車&#xff0c;慢得讓人著急&#xff1f;別急&#xff0c;我今天要給大家安利一個超好用的電腦優化神器——軟媒魔方&#xff01; 軟件介紹 首先&#xff0c;這貨真心是免費的&a…

upload-labs通關筆記-第19關文件上傳之條件競爭

目錄 一、條件競爭 二、源碼分析 1、源碼分析 2、攻擊原理 3、滲透思路 三、實戰滲透 1、構造腳本 2、制作圖片馬 3、獲取上傳腳本URL 4、構造訪問母狼腳本的Python代碼 5、bp不斷并發上傳母狼圖片馬 &#xff08;1&#xff09;開啟專業版bp &#xff08;2&#xf…

分布式消息隊列kafka詳解

分布式消息隊列kafka詳解 引言 Apache Kafka是一個開源的分布式事件流平臺&#xff0c;最初由LinkedIn開發&#xff0c;現已成為處理高吞吐量、實時數據流的行業標準。Kafka不僅僅是一個消息隊列&#xff0c;更是一個完整的分布式流處理平臺&#xff0c;能夠發布、訂閱、存儲…

uni-app(3):互相引用

1 絕對路徑和相對路徑 在日常開發中&#xff0c;經常會遇到使用絕對路徑還是相對路徑的問題&#xff0c;下面我們介紹下這兩種路徑。 1.1 絕對路徑 絕對路徑&#xff1a;是指從項目根目錄開始的完整路徑。它用于指定文件或目錄的確切位置。絕對路徑通常以斜杠&#xff08;/&am…

python與flask框架

一、理論 Flask是一個輕量級的web框架&#xff0c;靈活易用。提供構建web應用所需的核心工具。 Flask依賴python的兩個庫 Werkzeug&#xff1a;flask的底層庫&#xff0c;提供了WSGI接口、HTTP請求和響應處理、路由等核心功能。 Jinja2&#xff1a;模板引擎&#xff0…

esp32-idf框架學習筆記/教程

esp32型號: 環境搭建 安裝:就按這個來,別的試了好多次都不行,這個一次成功!!!! vscode下ESP32開發環境配置&#xff08;100%成功&#xff09;_嗶哩嗶哩_bilibili esp芯片的兩種模式: ESP32 固件燒錄教程_嗶哩嗶哩_bilibili 1.運行模式 2.下載模式 esp32s3程序下載 1.數據…

VKontakte(VK)注冊教程

VKontakte&#xff08;簡稱VK&#xff09;是俄羅斯最大的社交網絡平臺&#xff0c;類似于Facebook&#xff0c;用戶可以通過它進行社交、分享圖片、視頻、音樂等內容&#xff0c;并參與各類社群討論&#xff0c;是與俄羅斯及其他東歐地區的朋友建立聯系的便捷平臺。對于做俄羅斯…

STM32+ESP8266+ONENET+微信小程序上傳數據下發指令避坑指南

之前只做過類似的但是以為這種爛大街的功能應該不難結果還是踩了不少坑&#xff0c;記錄幾個需要注意的點 首先貼一個非常有用的視頻&#xff0c;里面講的很詳細&#xff0c;給的資料也很全【【新版OneNet云平臺】STM32ESP8266上傳數據&#xff0c;簡單易上手&#xff01;】 h…

【知識點】關于vue3中markRow、shallowRef、shallowReactive的了解

首先我們先了解一下這三個函數的定義以及區別 markRow 定義&#xff1a; 一個用于標記對象為非響應式的工具函數 shallowRef 定義&#xff1a; 一個用于創建淺層響應式引用的函數&#xff0c;只對 .value 本身進行響應式處理&#xff0c;不會遞歸地將 .value 指向的對象或…

后端開發實習生-抖音生活服務

職位描述 ByteIntern&#xff1a;面向2026屆畢業生&#xff08;2025年9月-2026年8月期間畢業&#xff09;&#xff0c;為符合崗位要求的同學提供轉正機會。 團隊介紹&#xff1a;生活服務業務依托于抖音、抖音極速版等平臺&#xff0c;致力于促進用戶與本地服務的連接。過去一…

OceanBase 共享存儲:云原生數據庫的存儲

目錄 探會——第三屆 OceanBase 開發者大會 重磅發布&#xff1a;OceanBase 4.3 開發者生態全面升級 實戰演講&#xff1a;用戶案例與行業落地 OceanBase 共享存儲架構解析 什么是共享存儲架構&#xff1f; 云原生數據庫的架構 性能、彈性與多云的統一 為何OceanBase能…

C++ 結構體封裝模式與 Promise 鏈式調用:設計思想的異曲同工

C 結構體封裝模式與 Promise 鏈式調用&#xff1a;設計思想的異曲同工 在軟件開發中&#xff0c;我們常常追求代碼的可維護性、可擴展性和可讀性。不同的編程語言和場景下&#xff0c;雖然實現方式各異&#xff0c;但背后的設計思想往往存在著奇妙的相似性。本文將探討 C 中結…

【Go】1、Go語言基礎

前言 本系列文章參考自稀土掘金上的 【字節內部課】公開課&#xff0c;做自我學習總結整理。 Go語言的特點 Go語言由Google團隊設計&#xff0c;以簡潔、高效、并發友好為核心目標。 具有以下優點&#xff1a; 語法簡單、學習曲線平緩&#xff1a;語法關鍵字很少&#xff0c;且…

AI時代的新營銷范式:生成式引擎優化(GEO)的崛起——品牌如何被大模型收錄

在數字化浪潮席卷全球的今天&#xff0c;我們正站在一個前所未有的歷史拐點。如果說過去二十年&#xff0c;搜索引擎優化&#xff08;SEO&#xff09;重塑了企業與消費者的連接方式&#xff0c;那么未來二十年&#xff0c;生成式引擎優化&#xff08;GEO&#xff09;將徹底顛覆…

實用藍牙耳機哪款好?先做好使用場景分析!

市面上的藍牙耳機款式繁多&#xff0c;618到來之際&#xff0c;消費者如何選擇適合自己的藍牙耳機&#xff1f;實用藍牙耳機哪款好&#xff1f;關鍵在于做好使用場景分析&#xff01;今天&#xff0c;就帶大家結合不同的使用場景&#xff0c;分享三款倍思音頻的精品藍牙耳機。 …