【C語言】指針(4):深入理解指針

目錄

?編輯

一、回調函數

二、qsort使用舉例

2.1 使用qsort排序整型數據

2.2 使用qsort排序結構體數據

三、qsort的模擬實現

四、NULL、\0、0、'0'、null、NUL的區別

五、C99中的變長數組

一、回調函數


? ? ? 函數指針是將函數的地址取出來,再通過函數地址去調用,那為什么不直接用函數名調用呢??原因是因為函數指針可以用來實現回調函數,而回調函數有自己的應用場景。

? ? ? 回調函數就是?個通過函數指針調?的函數。 如果你把函數的指針(地址)作為參數傳遞給另?個函數,當這個指針被?來調?其所指向的函數 時,被調?的函數就是回調函數。

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input){case 1:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}

以上這段代碼中,我們發現case部分的代碼總是重復出現,這段代碼只有調用函數的邏輯有差異(但是函數的返回類型和形參是一樣的),其他輸入輸出操作都是冗余的,那么這個時候我們可以把調用的函數地址以參數的形式傳去,用函數指針接收,函數指針指向什么函數就調用什么函數,這里其實就是使用的回調函數功能。

int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
void calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("輸入操作數:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main()
{int input = 1;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input){case 1:calc(add);break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}

? ?回調函數不是由該函數的實現方直接調?,?是在特定的事件或條 件發?時由另外的??調?的,?于對該事件或條件進?響應。

? ? ? 怎么理解上面這段話呢?我們可以發現回調函數并非直接調用的,而是當需要進行某種運算時(特定需求的發生),根據需求將函數地址傳給pf,然后在calc(另外一方)函數中通過pf(間接調用)來調用這個函數。

二、qsort使用舉例


前面學習的冒泡排序,只能排序整形數據,那我們如何完成其他數據的排序呢?就得用到qsort

qsort是一個庫函數,可以完成任意數據的排序,我們首先通過cplusplus的網站來了解qsort,qsort的頭文件是stdlib.h,下面我們能來分析他的形參類型。

1.第一個形參void*base是一個void*類型的指針(因為該數組可能是任意類型,所以只有void*才可以接收任意類型的數據的地址),base指向要排序的數組的第一個元素位置。

2.第二個形參size_t num是一個無符號整型,num指向的是待排序數組中的元素個數。(只要知道元素的個數才能確定比較的次數。)

3.第三個形參size_t size是一個無符號整型,size指向數組中元素的大小(單位是字節,因為qsort完成任何類型的排列,所以對象可能是結構體也可能是整型,需要具體傳入去 運算)。

4.第四個形參int (*compar)(const void*,const void*));,compar是一個函數指針,返回類型是int類型,兩個形參的類型是void*類型。該函數指針指向的函數是用來比較數組中兩個元素的方法。這個方法是根據我們的需求(比較整型或者比較結構體數據),去構造一個函數用來比較,構造的函數返回類型和形參類型必須一致。

qsort通過返回值來判斷p1和p2的大小,當返回值>0,說明p1大于p2,返回值=0,說明p1=p2,返回值<0,說明p1<p2。

了解了qsort,下面利用qsort來實現排序。

2.1 使用qsort排序整型數據

int int_cmp(const void* p1, const void* p2)//整型的比較方法
{return(*(int*)p1 - *(int*)p2);//void*類型的指針必須強轉后才可以進行運算。
}
int main()
{int arr[] = { 9,8,7,6,5,4,0,2,1,3 };int num = sizeof(arr) / sizeof(arr[0]);//確定數組的個數int size = sizeof(int);//確定數組的每個元素占用字節大小qsort(arr, num, size, int_cmp);for (int i = 0; i < num; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

運行結果:0 1 2 3 4 5 6 7 8 9 ?

注意事項:

1.qsort的使用必須包含頭文件stdlib.h

2創建比較方法int_cmp函數時要注意該函數返回的結果必須是>0,=0,<0;

3.int_cmp傳入的是void*類型的指針,必須強轉成int*類型再解引用才可以進行運算。

4.如果想要完成逆序,將int_cmp的代碼return(*(int*)p1 - *(int*)p2)中的p1和p2交換即可。

2.2 使用qsort排序結構體數據

struct Stu//學生
{char name[20];//名字int age;//年齡
};
//創建用年齡比較的方法
int cmp_stu_by_age(const void* p1, const void* p2)
{return((struct Stu*)p1)->age - ((struct Stu*)p2)->age;//也可以寫成(*(struct stu*)p1).age-(*(struct stu*)p2).age //結構體變量.成員名  或者   結構體指針->成員名
}
//創建用名字比較的方法
int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);//strcmp函數是專門用來比較字符串大小的//字符串的比較方法:從左到右的順序逐個比較兩個字符串的字符,直到遇到第一個不同的字符,然乎根據字符的ascii值來確定兩個字符串的大小關系。
}
//創建一個打印數組函數
void prinf(struct Stu s[], int num)
{for (int i = 0; i < num; i++){printf("第%d個同學的名字是%s,年齡是%d\n", i + 1, s[i].name, s[i].age);}
}
int main()
{struct Stu s[] = { {"zhangsan",20},{"lisi",30},{"wangwu",15} };int num = sizeof(s) / sizeof(s[0]);//元素個數int size = sizeof(struct Stu);//學生類型的大小printf("比較前\n");prinf(s, num);printf("通過年齡比較后\n");qsort(s, num, size, cmp_stu_by_age);prinf(s, num);printf("通過名字比較后\n");qsort(s, num, size, cmp_stu_by_name);prinf(s, num);
}

運行結果:

比較前
第1個同學的名字是zhangsan,年齡是20
第2個同學的名字是lisi,年齡是30
第3個同學的名字是wangwu,年齡是15
通過年齡比較后
第1個同學的名字是wangwu,年齡是15
第2個同學的名字是zhangsan,年齡是20
第3個同學的名字是lisi,年齡是30
通過名字比較后
第1個同學的名字是lisi,年齡是30
第2個同學的名字是wangwu,年齡是15
第3個同學的名字是zhangsan,年齡是20?

注意事項:

1.要訪問結構體成員的兩個方法:結構體變量.成員名 ? ?結構體指針->成員名

2.strcmp是專門用來比較字符串的大小的,并且它的返回值也恰好和qsort一樣,所以可以直接去調用。字符串的比較方法:從左到右的順序逐個比較兩個字符串的字符,直到遇到第一個不同的字符,然乎根據字符的ascii值來確定兩個字符串的大小關系。

3.結構體類型相較于整型類型,不能直接用+-<>等運算符,因為結構體中的成員屬性可能有多個,直接比較編譯器無法判斷根據哪一個成員屬性來比較。

?

三、qsort的模擬實現


? ? ? ?qsort展現的是不同數據類型的快速排序,在學習qsort之前,我只知道冒泡排序,而冒泡排序只能排序整型類型,那么我們可以通過會回調函數的方法,來改造冒泡排序,使其成為可以排序任意數據類型的排序方法。

? ? ?在模擬實現前,我們要比較qsort和冒泡排序,兩者的數據類型不一樣,所以我們對他的改造需要體現在兩個方面。

1.由于數據類型不同,所以比較的方法必須改造。

2.由于不同數據類型占用字節大小不同,在利用指針偏移量操作的時候會有差異,所以交換的方法也必須改造。

3.由于數據類型不同,創建比較方法和交換方法時傳入的兩個參數必須是void*類型

4.模擬實現qsort,就要保證改造的排序函數bubble的返回類型和形參都要保持一致

int int_cmp(const void* p1, const void* p2)//比較方法
{return(*(int*)p1 - *(int*)p2);
}
void swap(void* p1, void* p2, int size)//交換方法,這里要引入size,讓swap函數知道交換的數據是什么類型
{for (int i = 0; i < size; i++){char temp = *((char*)p1+ i);//void*類型必須要先強制轉化成char*類型*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = temp;//為什么這里要使用字符類型?,因為我們并不知道傳入的是什么數據類型,所以用char*(1個字節)來作為單位元,每次交換一個字節,交換次數恰好和size相同}
}
void bubble(void* base, int num, int size, int (*cmp)(const void* p1, const void* p2))
{for (int i = 0; i < num - 1;i++){for (int j = 0; j < num - i - 1; j++){if (int_cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)//不知道是什么數據類型,所以用char*比較好操作,一次只操作一個字節。{swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}//使用前必須強轉成char*類型}}
}
int main()
{int arr[] = { 9,8,7,6,5,4,0,2,1,3 };int num = sizeof(arr) / sizeof(arr[0]);//確定數組的個數int size = sizeof(int);//確定數組的每個元素占用字節大小bubble(arr, num, size, int_cmp);for (int i = 0; i < num; i++){printf("%d ", arr[i]);}printf("\n");return 0;
}

運行結果:0 1 2 3 4 5 6 7 8 9?

? ? ? ?要注意的是,由于交換方法和比較方法的改造,由于不知道比較的是什么數據類型,所以都強轉成char*類型進行操作,因為char*類型操作一次是一個字節,方便計算。這樣恰好就是一次交換一個字節,執行size次后就完成整個元素的交換。所以必須傳入size。

?

四、NULL、\0、0、'0'、null、NUL的區別


NULL:本質是0,一般用于指針的初始化

\0:\ddd形式的轉移字符,本質也是0,在字符串中作為結束標志,ASCII碼值為0

0:數字0

'0':字符0,ASCII碼值為48

null/NUL:本質就是\0,作為字符串結束標志

五、C99中的變長數組


? ? ? ? 在C99標準之前,C語?在創建數組的時候,數組大小的指定只能使?常量、常量表達式,或者如果我們初始化數據的話,可以省略數組??。

int arr1[10];
int arr2[3+5];
int arr3[] = {1,2,3};

?這樣的語法限制,讓我們創建數組就不夠靈活,有時候數組?了浪費空間,有時候數組??了不夠?的。?

? ? ?C99中給?個變?數組(variable-length array,簡稱 VLA)的新特性,允許我們可以使?變量指定數組大小。

int n = a+b;
int arr[n];

上??例中,數組 arr 就是變?數組,因為它的?度取決于變量 n 的值,編譯器沒法事先確定,只有運?時才能知道 n 是多少。

? ?變?數組的根本特征,就是數組?度只有運?時才能確定,所以變?數組不能初始化。它的好處是程序員不必在開發時,隨意為數組指定?個估計的?度程序可以在運?時為數組分配精確的?度。有 ?個?較迷惑的點,變?數組的意思是數組的??是可以使?變量來指定的,在程序運?的時候,根據變量的??來指定數組的元素個數,?不是說數組的??是可變的。數組的大小?旦確定就不能再變化了。

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

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

相關文章

untiy 在菜單欄添加自定義按鈕 點擊按鈕彈出一個Unity窗口,并在窗口里添加屬性

using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.PostProcessing;public class AutoGenerateWindow : EditorWindow //這是定義一個窗口 {public string subjecttName "科目名字";//科目的名字public GameOb…

springboot 與 ipv6

ipv6 是個必然趨勢&#xff0c;尤其最近國家在這方面有新的推動。 運營商的項目逐漸有這方面的要求了。 所以&#xff0c;在ipv6環境&#xff0c;http或者https接口&#xff0c;還有數據庫地址&#xff0c;ipv4下是ip:port&#xff0c; 但到了ipv6&#xff0c;ipv6 的 ip就包含…

PIOMAS二進制文件轉nc文件

文章目錄 1. 按年輸出數據2. 按月輸出數據將PIOMAS標量的二進制數據格式轉化成nc格式。 1. 按年輸出數據 # 按年輸出數據 import numpy as np import pandas as pd import struct import xarray as xr import matplotlib.pyplot as plt # from cartoplot import cartoplotgri…

另一種加快大表查詢的方法:將表分區

在 MySQL 中&#xff0c;對表進行分區是一種將大表分成更小、更易于管理和查詢片段的方式。分區能夠顯著提升查詢和維護的性能&#xff0c;特別是對大數據量的表。以下是 MySQL 表分區的基礎知識和具體操作步驟。 分區類型 MySQL 支持如下主要分區類型&#xff1a; RANGE 分…

url鏈接地址,#前的參數 和 #后的參數有什么區別

例如 http://localhost:8080/?beforeParams1#/workSchemelist/index?afterParams1 beforeParams 和 afterParams 區別 打印出來可以發現&#xff1a; beforeParams 是 url 的search參數&#xff0c;通過window.location.search獲取 afterParams 是 route 的query參數&#…

行列視(RCV)是否支持自定義字段、計算公式和數據分析功能,以滿足用戶的不同需求?

行列視&#xff08;RCV&#xff09;確實支持自定義字段、計算公式和數據分析功能&#xff0c;以滿足用戶的不同需求。具體表現如下&#xff1a; 1. 自定義字段&#xff1a;RCV提供自助式數據應用&#xff0c;允許用戶根據自己的需求&#xff0c;選擇所需的字段來構建符合自己業…

外貿網站設計的要點

外貿網站設計是一種專門針對國際貿易領域的網站設計&#xff0c;需要考慮到不同國家和文化背景的用戶&#xff0c;因此設計過程要更加細致和精準。以下是外貿網站設計的關鍵要點&#xff1a; 首先&#xff0c;多語言支持是不可或缺的&#xff0c;因為外貿網站的用戶可能來自不同…

[Python自動化辦公]--從網頁登錄網易郵箱進行郵件搜索并下載郵件附件

[Python自動化辦公]–從網頁登錄網易郵箱進行郵件搜索并下載郵件附件 使用說明 ? 本文使用Python的selenium庫進行操作郵箱登錄、固定名稱搜索郵件并下載附件&#xff0c;Python版本&#xff1a;3.9.16, selenium版本&#xff1a;4.19.0&#xff0c;EdgeBrowser版本:126.0.2…

LVS集群及其它的NAT模式

1.lvs集群作用&#xff1a;是linux的內核層面實現負載均衡的軟件&#xff1b;將多個后端服務器組成一個高可用、高性能的服務器的集群&#xff0c;通過負載均衡的算法將客戶端的請求分發到后端的服務器上&#xff0c;通過這種方式實現高可用和負載均衡。 2.集群和分布式&#…

用戶增長 - 私域 - 社群運營自檢清單SOP(社群運營30問)

Check List: 1.你的目標用戶是誰&#xff1f; 2.你的目標用戶有哪些需要立馬解決的需求&#xff1f;有哪些長期需求&#xff1f;這些需求的優先級是什么&#xff1f; 3.做社群的目的是什么&#xff1f; 4.你的用戶和業務是否適合做社群&#xff1f; 5.你做哪類社群才能更好的幫…

確定適合您需求的負載組

大多數關鍵任務行業都使用 UPS 和發電機等備用電源在停電期間為其設施提供持續電力。負載組允許您在需要時測試電源&#xff0c;以確保在您最需要的時候提供可靠的電力。 選擇正確的負載組對于準確的電源測試至關重要。為了幫助您找到最適合您設施需求的負載組&#xff0c;EAK…

Hudi 索引總結 - Parquet布隆過濾器寫入過程

前言 上篇文章 提到 :索引的邏輯主要是根據 parquet 文件中保存的索引信息,判斷記錄是否存在,如果不存在,代表是新增數據,如果記錄存在則代表是更新數據,需要找到并設置 currentLocation。對于布隆索引來說,這里的索引信息其實是布隆過濾器,本篇文章主要是先總結布隆過…

【機器學習】主成分分析(PCA):數據降維的藝術

&#x1f308;個人主頁: 鑫寶Code &#x1f525;熱門專欄: 閑話雜談&#xff5c; 炫酷HTML | JavaScript基礎 ?&#x1f4ab;個人格言: "如無必要&#xff0c;勿增實體" 文章目錄 主成分分析&#xff08;PCA&#xff09;&#xff1a;數據降維的藝術引言PCA的基…

技術成神之路:設計模式(四)工廠方法模式

1.定義 工廠方法模式&#xff08;Factory Method Pattern&#xff09;是一種創建型設計模式&#xff0c;它提供了一種創建對象的接口&#xff0c;而不是通過具體類來實例化對象。工廠方法模式的主要作用是讓子類決定實例化哪一個類&#xff0c;從而實現對象創建的延遲到具體子類…

2024年6月國產數據庫大事記-墨天輪

本文為墨天輪社區整理的2024年6月國產數據庫大事件和重要產品發布消息。 目錄 2024年6月國產數據庫大事記 TOP102024年6月國產數據庫大事記&#xff08;時間線&#xff09;產品/版本發布兼容認證代表廠商大事記廠商活動相關資料 2024年6月國產數據庫大事記 TOP10 2024年6月國…

最優雅的PHP框架 Laravel

Laravel 之所以被稱為最優雅的 PHP 框架,是因為它在設計和功能上做了很多獨特的創新,極大地提高了開發效率和代碼的可維護性。以下是 Laravel 受歡迎的主要原因: 良好的文檔和社區支持 Laravel 有詳盡的官方文檔,涵蓋了框架的所有功能和用法。此外,Laravel 社區非常活躍…

【Python】已解決:SyntaxError invalid syntax

文章目錄 一、分析問題背景二、可能出錯的原因三、錯誤代碼示例四、正確代碼示例五、注意事項 已解決&#xff1a;SyntaxError invalid syntax 一、分析問題背景 在Python編程中&#xff0c;SyntaxError: invalid syntax是一個常見的錯誤&#xff0c;它通常表示代碼中存在語法…

.net開發:NPOI生成excel文件到磁盤

源碼實測可用 使用.net工具包NPOI&#xff0c;生成excel文件到本地磁盤。 實際項目中可以指定路徑到服務器&#xff0c;把生成的文件存放到服務器指定目錄。 controller層 [HttpPost("ExportExcel")]public void ExportExcel(){_TestService.ExportToExcel();} serv…

redis中的事務和mysql中的事務有什么區別?

Redis和MySQL的事務在概念和特性上存在一些顯著的區別&#xff1a; 1. 原子性&#xff08;Atomicity&#xff09;: - MySQL&#xff1a;事務具有原子性&#xff0c;即事務中的所有操作要么全部成功&#xff0c;要么全部失敗&#xff0c;如果中途出現錯誤&#xff0c;整個事…

Linux內核 -- 虛擬化之virtqueue結構

Linux Kernel中的Virtqueue Virtqueue是Linux Kernel中用于實現Virtio設備的一個關鍵數據結構。Virtio是一種虛擬I/O設備標準&#xff0c;旨在簡化虛擬化環境中虛擬設備與虛擬機之間的通信。Virtqueue則是實現這種通信的核心機制。以下是Virtqueue的一些關鍵點&#xff1a; V…