摘要:讓我們來看一下代碼,首先我們還是冒泡排序一樣,進(jìn)行了兩次循環(huán),第一次代表排序趟數(shù),第二次代表每趟的排序次數(shù)。這塊的詳細(xì)介紹在本篇文章稍前的冒泡排序中也有詳細(xì)介紹。
數(shù)組名在除了定義數(shù)組,使用sizeof()函數(shù)和加&之外均代表數(shù)組首元素的地址
int (*p2)[10] = &arr;
我們首先來看看這個(gè)代碼,*p2是一個(gè)指針,在代碼中p2外的括號(hào)不可缺少的,因?yàn)閇]的優(yōu)先級(jí)更高所以如果不加括號(hào),這行代碼就會(huì)變成一個(gè)指針數(shù)組的定義
int Add(int x, int y){ return x + y;}
假設(shè)我們要用指針來存儲(chǔ)這個(gè)加法函數(shù)的地址,首先我們要知道函數(shù)名就代表函數(shù)的地址
int (*pf)(int, int) = Add;//pf是指針名,最前面的int代表函數(shù)的返回類型,后面的兩個(gè)int表示函數(shù)的參數(shù)類型
值得注意的是,代表函數(shù)的參數(shù)類型的int后面加上x或y或者任意字符都是可以的,只要能明確其類型就行,但這沒有必要
因?yàn)楹瘮?shù)名就代表函數(shù)的地址,所以事實(shí)上函數(shù)的指針與函數(shù)名是等價(jià)的,所以我們?cè)谡{(diào)用函數(shù)指針時(shí)既可以將函數(shù)指針解引用為原函數(shù)名來使用函數(shù),也可以使用指針來直接調(diào)用,如
// 先解引用為原函數(shù)名int sum = (*pf)(2,3);// 直接使用指針sum = pf(2,3);
我們可以發(fā)現(xiàn)在第一個(gè)調(diào)用過程中,*其實(shí)只是一個(gè)擺設(shè)
顧名思義,該數(shù)組就是用來存放函數(shù)指針的數(shù)組(也可以說數(shù)組中存的就是函數(shù)名),那么我們要如何來使用這個(gè)東西呢?現(xiàn)在咱們?cè)僭黾右粋€(gè)減法函數(shù)
int Sub(int x, int y){ return x-y;}
那么比方說我們想要來完成一個(gè)能實(shí)現(xiàn)加減法的計(jì)算器功能,那么我們可以先進(jìn)行如下代碼
int (*pA)(int, int) = Add;int (*pS)(int, int) = Sub;
我們會(huì)發(fā)現(xiàn)這兩個(gè)函數(shù)無論是參數(shù)還是返回值的類型都是相同的,所以我們可以考慮使用一個(gè)指針數(shù)組來實(shí)現(xiàn)代碼,讓我們這樣定義函數(shù)指針數(shù)組
int (*pArr[5])(int, int) = {Add, Sub};//因?yàn)閇]的優(yōu)先級(jí)高于*,所以當(dāng)指針名不加括號(hào)時(shí)pArr就代表一個(gè)函數(shù)指針數(shù)組
更為深入地來看,我們可不可以獲取并存儲(chǔ)pArr的地址呢?答案是可以的
int (*(*p)[5])(int, int) = &pArr;//其中p是一個(gè)指針指向了一個(gè)元素為函數(shù)指針的數(shù)組
到這里套娃的工作我們就不繼續(xù)了,讓我們?cè)賮砜纯椿卣{(diào)函數(shù)
回調(diào)函數(shù)就是用一個(gè)函數(shù)來調(diào)用另一個(gè)函數(shù),以實(shí)現(xiàn)在特定條件下調(diào)用某個(gè)函數(shù)的目的?,F(xiàn)在我們有一個(gè)Calc()函數(shù),我們要求當(dāng)我們把Add函數(shù)傳過去時(shí)就完成加法,將Sub傳過去時(shí)就完成減法,
void Calc(int (*pf)(int, int)){int ret = pf(5,3);printf("%d", ret);}int main(){Calc(Add);//既然我們傳輸了函數(shù)的地址,我們就要用一個(gè)函數(shù)指針來接收Calc(Sub); return 0;}
這就是一個(gè)簡(jiǎn)單的回調(diào)函數(shù),當(dāng)把Add傳參給Calc時(shí),pf就接收了Add的地址,并用Add來計(jì)算5+3的值,Sub同理來計(jì)算5-3的值
我們擁有很多種排序方法,如冒泡排序,選擇排序,插入排序等,這次我想主要來講qsort快速排序,并在簡(jiǎn)單說完冒泡排序后用冒泡排序來模擬qsort函數(shù)
我們先來簡(jiǎn)單地講講冒泡排序,冒泡排序的基本思想無非是兩兩比較大小并排序,,比方說我想把{9,8,7,6,5,4,3,2,1,0}這10個(gè)數(shù)字改為升序應(yīng)該怎么做呢
void BubbleSort(int arr[], int sz) { int i = 0; for(i = 0; i < sz - 1; i++)//我們每輪會(huì)改變一個(gè)數(shù)字的位置,因?yàn)楫?dāng)我們把之前的9個(gè)數(shù)字排列完之后最后一個(gè)數(shù)字的位置也必然已經(jīng)正確了,故只需排列比數(shù)組大小少一輪 { int j = 0; for(j = 0; j <= sz - i -1; j++);//因?yàn)槊枯嗊^后都會(huì)有一個(gè)數(shù)字已經(jīng)排序完成且位于數(shù)列后方,所以每輪之后需要排列的數(shù)字都可以減1 { if(arr[j] > arr[j+1]) { int tmp = arr[j+1]; arr[j+1] = arr[j]; arr[j] = tmp; } } }}int main(){ int arr[10] = {9,8,7,6,5,4,3,2,1,0}; BubbleSort(arr,sz);//當(dāng)我們要進(jìn)行排序的時(shí)候一般會(huì)考慮將數(shù)組的大小也傳送過去}
這就是一個(gè)冒泡排序,如果想看它的結(jié)果,我們也可以再添加一個(gè)打印程序
PrintArr(int arr, int sz){ int i = 0; for(i = 0; i < sz; i++) { printf("%d ", arr[i]); }}
對(duì)于冒泡排序,我們會(huì)發(fā)現(xiàn)它只能用于對(duì)整型的排序,那么如果我們想讓一個(gè)程序來排列任意類型時(shí),應(yīng)該怎么辦呢,qsort函數(shù)能幫我們解決這個(gè)難題,下面我們來講講qsort函數(shù)
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
這是qsort函數(shù)的格式規(guī)范,看起來很長(zhǎng)很復(fù)雜,但當(dāng)我們把它給細(xì)分出來時(shí)就顯得很簡(jiǎn)單了,讓我們把它分行寫出來,
void qsort( void base,
size_t num,
size_t width,
int (cmp)(const void e1, const void e2 ) );
之前說過,我們排序時(shí)除了傳送數(shù)組名外我們還會(huì)把數(shù)組大小也給傳送,但在這個(gè)函數(shù)中,我們可以發(fā)現(xiàn)這個(gè)函數(shù)第一個(gè)參數(shù)是數(shù)組,第二個(gè)參數(shù)是數(shù)組大?。ㄔ貍€(gè)數(shù)),那么之后兩個(gè)參數(shù)是什么東西?
要回答這個(gè)問題,我們首先要注意一下base前的類型是void,void是一種無具體類型的指針,可以理解為通用指針,因?yàn)槲覀兿M鹮sort函數(shù)能夠排序任意類型的數(shù)組,所以我們就需要void的幫助
重點(diǎn)來了!
void能接收任意類型的地址,但它有一個(gè)缺點(diǎn):不能進(jìn)行運(yùn)算,不能解引用,因?yàn)橹羔樳M(jìn)行運(yùn)算時(shí)依賴指針類型來明確指針?biāo)赶蝾愋偷膬?nèi)存大小,比如若p是一個(gè)整型指針,那么p++增加的就是4bit,而當(dāng)p是字符指針時(shí),p++增加的就只是1bit,而void*未明確指針指向的類型,自然無法進(jìn)行運(yùn)算了,所以我們就能理解第三個(gè)參數(shù)存在的意義了,它代表的是每個(gè)元素類型所占的內(nèi)存大小,即寬度,這樣qsort函數(shù)就可以知道每個(gè)元素的內(nèi)存大小以便排序的進(jìn)行了
現(xiàn)在我們?yōu)閝sort函數(shù)傳輸了數(shù)組,元素個(gè)數(shù)和元素大小,按道理來說這已經(jīng)足以讓qsort進(jìn)行排序了,那第四個(gè)參數(shù)有什么作用呢?你一定想到了,我們當(dāng)初使用qsort的目的之一就是為了排序任意類型的元素,所以我們必須對(duì)種類型的元素定義一個(gè)特定的排序規(guī)則(先提一嘴,第四個(gè)參數(shù)就是一個(gè)函數(shù)指針,也就是說我們需要?jiǎng)?chuàng)建一個(gè)函數(shù)并傳遞它的指針),比方說我現(xiàn)在想要給一個(gè)結(jié)構(gòu)體排序,
Struct Stu{ char name[20]; int age; float score;}; struct Stu s[3] = { {"張三",15,70.0},{"李四",35,72.0},{"王五",55,14.0} };
顯然我們不能簡(jiǎn)單地比較三個(gè)結(jié)構(gòu)體的大小,所以我們必須確立一個(gè)規(guī)則,確定應(yīng)該使用名字,年齡還是分?jǐn)?shù)來對(duì)三位學(xué)生進(jìn)行排序,我們先來看看qsort對(duì)這個(gè)排序規(guī)則的要求
返回值 | 描述 |
---|---|
返回值小于0 | e1小于e2 |
返回值等于0 | e1等于e2 |
返回值大于0 | e1大于e2 |
也就是說當(dāng)e1小于e2時(shí),cmp需要返回一個(gè)負(fù)數(shù)作為qsort的參數(shù),等于時(shí)返回0,大于時(shí)返回正數(shù),這個(gè)很簡(jiǎn)單,只需要將e1-e2即可,現(xiàn)在讓我們看看如果想用名字來進(jìn)行排序應(yīng)該怎么操作
#define _CRT_SECURE_NO_WARNINGS#include #include #include struct Stu{ char name[20]; int age; float score;};int cmp(const void* e1, const void* e2){ return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);//因?yàn)関oid*類型不能進(jìn)行運(yùn)算,所以我們把它強(qiáng)制轉(zhuǎn)換為struct Stu*類型,取其中的name,并用strcmp函數(shù)來比較字符串大小}int main(){ struct Stu s[3] = { {"張三",15,70.0},{"李四",35,72.0},{"王五",55,14.0} }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp);//這里實(shí)際上就是一個(gè)回調(diào)函數(shù),qsort回調(diào)了cmp,cmp接收了類型為const void*的指針e1和e2 return 0;}
同理,如果我們想用年齡來進(jìn)行排序,我們只需將cmp函數(shù)修改為
return ((struct Stu*)e1)->age-((struct Stu*)e2)->age;
而如果我們想實(shí)現(xiàn)倒敘則只需要把cmp中前后兩個(gè)參數(shù)調(diào)換位置即可。
現(xiàn)在我們已經(jīng)可以理解冒泡排序和qsort的用法,那么如何使用冒泡排序來模擬qsort的功能呢?
現(xiàn)在筆者先把整體代碼展現(xiàn)一下,接著再對(duì)每塊代碼細(xì)細(xì)解釋
void print_arr(int arr[], int sz){ int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("/n");}int cmp_int(const void* e1, const void* e2){ return((*(int*)e1) - (*(int*)e2));}void swap(char* buf1, char* buf2, int width){ int i = 0; for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; }}void BubbleSort(void* base, size_t num, size_t width, int (*cmp_int)(const void* e1, const void* e2)){ size_t i = 0; for (i = 0; i < num - 1; i++) { size_t j = 0; for (j = 0; j < num - 1 - i; j++) { if (cmp_int((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } }}int main(){ int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); BubbleSort(arr, sz, sizeof(arr[0]), cmp_int); print_arr(arr, sz); return 0;}
現(xiàn)在讓我們來理一下這些代碼的思路。
這里的BubbleSort()其實(shí)就相當(dāng)于qsort(),和qsort一樣,我們也傳入了4個(gè)參數(shù),分別是數(shù)組,元素個(gè)數(shù),元素大小和排序函數(shù),他們的作用也和qsort函數(shù)中的一樣,我就不一一贅述了。讓我們來看一下代碼,首先我們還是冒泡排序一樣,進(jìn)行了兩次循環(huán),第一次(i)代表排序趟數(shù),第二次(j)代表每趟的排序次數(shù)。(這塊的詳細(xì)介紹在本篇文章稍前的冒泡排序中也有詳細(xì)介紹。)所以讓我們馬上來看看這個(gè)函數(shù)的不同之處,讓我們接著看代碼,
if (cmp_int((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width, width); }
在這里我們使用了兩個(gè)函數(shù),cmp_int和swap,其中cmp_int的功能是對(duì)數(shù)列前后兩個(gè)元素進(jìn)行大小比較,swap則負(fù)責(zé)對(duì)降序的元素位置進(jìn)行交換。
類似于qsort函數(shù),在這個(gè)函數(shù)中我們也希望這個(gè)比較函數(shù)能夠在升序時(shí)返回負(fù)數(shù),降序時(shí)返回一個(gè)正數(shù),所以我們只需要將數(shù)組中的前后兩個(gè)元素依次傳參,并用前一個(gè)元素減去后一個(gè)元素,并將該值作為函數(shù)的返回值。而其中我們?yōu)榱四軌蚴惯@個(gè)比較函數(shù)適用于各種類型的數(shù)組,我們?cè)趥鲄r(shí)將參數(shù)類型設(shè)成了char*,所以在這個(gè)為int類型比較大小的函數(shù)里,我們可以把它強(qiáng)制類型轉(zhuǎn)換為int*(當(dāng)然在字符比較函數(shù)里我們可以將它強(qiáng)制轉(zhuǎn)換為char*)
如果知道這個(gè)函數(shù)中有兩個(gè)元素是降序,我們就要將其調(diào)換位置,在傳參時(shí),為了函數(shù)的通用性,我們依然傳遞了char類型的參數(shù),并用char進(jìn)行接收,所以我們絕對(duì)就不能直接調(diào)換元素的位置,而具體的調(diào)換方法呢,我們就用9和8這兩個(gè)元素代替,我們知道在小端存儲(chǔ)中,9和8的內(nèi)存分別為09 00 00 00和08 00 00 00且他們的內(nèi)存是連續(xù)的
我們想要把09和08,09后的00和08后的00,09后的00后的00······(此處省略一萬字)交換位置,所以現(xiàn)在思路就有了,我們首先獲得09和08元素的第一個(gè)字節(jié)的地址,交換位置后向后調(diào)整一個(gè)字節(jié),直到這個(gè)元素的所有字節(jié)被調(diào)換完,對(duì)于int類型,它的大小是四個(gè)字節(jié),所以我們需要對(duì)每個(gè)元素調(diào)換四次位置
void swap(char* buf1, char* buf2, int width){ int i = 0;//i代表調(diào)換次數(shù),這里我們希望他循環(huán)4次 for (i = 0; i < width; i++) { //調(diào)換 char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; }}
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/119810.html
摘要:函數(shù)的返回值為指針就按照字面意思,指針函數(shù)的定義顧名思義,指針函數(shù)即返回指針的函數(shù)。 目錄 前言指針與函數(shù)函數(shù)的返回值為指針作為函數(shù)參數(shù)的指針指針函數(shù)可以改變變量...
摘要:作者陳大魚頭鏈接背景最近高級(jí)前端工程師劉小夕在上開了個(gè)每個(gè)工作日布一個(gè)前端相關(guān)題的,懷著學(xué)習(xí)的心態(tài)我也參與其中,以下為我的回答,如果有不對(duì)的地方,非常歡迎各位指出。 作者:陳大魚頭 github: KRISACHAN 鏈接:github.com/YvetteLau/S… 背景:最近高級(jí)前端工程師 劉小夕 在 github 上開了個(gè)每個(gè)工作日布一個(gè)前端相關(guān)題的 repo,懷著學(xué)習(xí)的心態(tài)我也參...
摘要:釋放不完全導(dǎo)致內(nèi)存泄漏。既然把柔性數(shù)組放在動(dòng)態(tài)內(nèi)存管理一章,可見二者有必然的聯(lián)系。包含柔性數(shù)組的結(jié)構(gòu)用進(jìn)行動(dòng)態(tài)內(nèi)存分配,且分配的內(nèi)存應(yīng)大于結(jié)構(gòu)大小,以滿足柔性數(shù)組的預(yù)期。使用含柔性數(shù)組的結(jié)構(gòu)體,需配合以等動(dòng)態(tài)內(nèi)存分配函數(shù)。 ...
摘要:所以是數(shù)組指針,而是指針數(shù)組。因?yàn)閷?duì)一個(gè)二維數(shù)組,可以不知道有多少行,但是必須知道一行多少元素。當(dāng)二維數(shù)組數(shù)組名傳參,形參接收時(shí),數(shù)組的行可以省略,列不能省略,如果省略了列,我們就無法知道當(dāng)指針加減跳過幾個(gè)字節(jié)。 ...
摘要:本章節(jié)在此基礎(chǔ)上,對(duì)語言階段指針進(jìn)行更深層次的研究。數(shù)組指針的類型由數(shù)組類型決定,先找出數(shù)組的類型去掉名就是類型。相當(dāng)于數(shù)組指針?biāo)赶驍?shù)組的數(shù)組名。數(shù)組指針指向整個(gè)數(shù)組,將其看作二維數(shù)組并解引用得到一行的首元素,從而遍歷訪問。 ...
閱讀 741·2023-04-25 19:28
閱讀 1400·2021-09-10 10:51
閱讀 2397·2019-08-30 15:55
閱讀 3419·2019-08-26 13:55
閱讀 3008·2019-08-26 13:24
閱讀 3335·2019-08-26 11:46
閱讀 2763·2019-08-23 17:10
閱讀 1424·2019-08-23 16:57