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