C語言庫函數之 qsort 講解、使用及模擬實現

引入

我們在學習排序的時候,第一個接觸到的應該都是冒泡排序,我們先來復習一下冒泡排序的代碼,來作為一個鋪墊和引入。

代碼如下:

#include<stdio.h>void bubble_sort(int *arr, int sz)
{int i = 0;for (i = 0; i < sz - 1; i++){int j = 0;for (j = 0; j < sz - 1 - i; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}

很簡單的一種排序方法,但我們可以發現一個問題,那就是冒泡排序不夠通用,它只能用于整型數組的排序,如果我要排序float類型,或者排序結構體要怎么辦呢。

下面,我們就來介紹一個比較萬能的排序函數,qsort函數

簡介

先來簡單了解一下qsort函數的各個部分

語法格式

它的固定格式如下:

int cmp_int(const void* e1, const void* e2)
{}

注意:他的格式是固定的,比如返回值類型必須是int類型,形參的類型也是固定的,只有返回值的部分是自己編寫的,也就是說,當返回值類型不是int的時候,我們需要進行強制類型轉換或者手動將其返回值改成int類型
(這在下文會詳細說明)

參數解釋

在調用函數時,傳參格式如下:

void qsort(void* base,size_t num,size_t width,int (*cmp)(const void* e1, const* e2)
);

可以看一下這張圖,里面講解了qsort函數的各個參數分別表示的是什么,
在這里插入圖片描述

base:起始位置,待排序數組的首元素地址 num:數組的大小,單位是元素,待排序數組的元素個數
width:元素大小,單位是字節,待排序數組的單個元素的大小 cmp:函數指針(比較函數:compare
function),比較兩個元素的函數的地址
解釋:對于不同類型元素的比較的方法是不同的,此處就是將兩個元素的比較方法寫成函數,傳到qsort函數中,然后使用指針cmp進行調用
e1和e2可以簡單地認為是要比較的兩個元素的地址,(下面會做補充說明)

對void *的解釋

先拋出一個問題:下面這個代碼有什么問題

int main()
{int a = 0;int *pa = &a;char* pc = &a; return 0;
}

問題就是:第四行和第五行:此處雖然可以存儲,但會報警告:從“int *”

到“char *”的類型不兼容。

那么,我們這時就可以使用,void *(無指針類型)來解決這個問題

void* p = &a;
//void *類型的指針可以接收任意類型的地址

此處就可以很好地解釋qsort函數的第一個參數:void* base

補充:

對于void *類型的指針無法進行解引用
因為不知道進行解引用之后,要訪問幾個字節
同理,也無法進行無符號型的指針與整數的運算

那么,在qsort函數中,如何比較e1與e2呢
可以將二者強制轉換成所需的類型(代碼中會提到這一點)

返回值

下圖是英文版的介紹
在這里插入圖片描述

對qsort函數返回值的解釋
當e1<e2,返回值小于0
當e1=e2,返回值等于0
當e1>e2 返回值大于0

提示:所以可以利用這個規律將不是int類型的返回值手動變成int型(下文float類型那里會詳細說明)

使用

此處我們舉三個例子,分別是int、float和結構體類型變量的比較

int類型

明確需要

我們需要三個函數:main函數(調用test函數),test函數(調用qsort函數、打印最終結果),和cmp_int函數(提供元素的比較方法)

test函數

1.創建數組
2.計算大小
3.調用qsort函數
4.打印最終結果

cmp_int函數

照著前面的固定格式,然后返回值那里就直接用

(這里我一寫*,他就識別成斜體,大家直接看下面的代碼吧…)

最終代碼

#include<stdlib.h>int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;}void test1()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}int main()
{test1();return 0;
}

還是比較好理解的,就不做過多解釋了

float類型

對于cmp_float函數的說明

問題

我們知道cmp_float函數的返回值必須是int類型的,但,

*(float*)e1 - *(float*)e2

的返回類型是float類型,在運行時會報一個警告:return”: 從“float”轉換到“int”,可能丟失數據,
此處提供兩種解決方法:

1.使用if else語句手動判斷大小并根據情況分別返回一個負數、0、一個正數
代碼如下:

if (*(float*)e1 > *(float*)e2)
{return 1;
}
else if (*(float*)e1 == *(float*)e2)
{return 0;
}
else
{return -1;
}

2.使用強制類型轉換,轉換成int類型

最終代碼


#include<stdlib.h>int cmp_float(const void* e1, const void* e2)
{return *(float*)e1 - *(float*)e2;
}void test2()
{float f[] = { 9.0, 8.0, 7.0, 6.0 ,5.0 ,4.0 ,3.0, 2.0, 1.0 };int sz = sizeof(f) / sizeof(f[0]);qsort(f, sz, sizeof(f[0]), cmp_float);int i = 0;for (i = 0; i < sz; i++){printf("%.3f ", f[i]);}
}int main()
{test2();return 0;
}

結構體類型

如果我們想要排序結構體類型的變量,那就很有意思了,我們一步一步來分析

明確需要

main函數、test3函數、cmp_stu函數
下面我們重點解釋一下test3函數和cmp_stu函數

test3函數

1.創建結構體類型的數組,并初始化
2.求數組元素個數
3.調用qsort函數,里面包含了cmp_stu函數的地址,即調用cmp_stu函數

cmp_stu函數

照貓畫虎

我們按照前面的兩個例子寫出來的應該是

int cmp_struct(const void* e1, const void* e2)
{return *(struct*)e1 - *(struct*)e2;}

但這么寫是錯誤的, 因為結構體是復雜對象,無法直接用 > 或 < 進行比較,那么我們就需要確定是用哪個成員去作為比較的標準
再通過->來訪問相應的成員

下面給出兩個例子,此處分別以年齡age作為排序標準和以名字name來排序,

cmp_stu_by_age函數

將e1和e2從void* 類型轉換成結構體類型指針,然后再通過->訪問相應的成員
然后直接讓二者相減即可,此處在強制類型轉換時別忘了寫上struct就行

int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;}

這個函數的實現還是與cmp_int函數有一些相似的,

cmp_stu_by_name函數

提示:此處比較的是字符串,同樣不能用 > < 來進行比較
而是要用strcmp函數進行比較,包含頭文件<string.h>

int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);}

最終代碼

#include<stdlib.h>struct Stu
{char name[40];int age;
};int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;}int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);提示,此處比較的是字符串,同樣不能用 > < 來進行比較而是要用strcmp函數進行比較
}void test3()
{struct Stu s[3] = { {"zhang", 20},{"li", 30},{"wang", 40} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}int main()
{test3();return 0;
}

三個例子到這里就介紹結束了,其實這樣看下來也不是很難理解。
下面我們來學習qsort函數的模擬實現,也就是優化bubble_sort函數,使它能排序任意類型的元素

模擬實現

引入:

此處提出一個問題:如果說,我不想使用qsort函數,我就想使用冒泡函數,那我要如何改進它,才能達到和qsort函數相同的效果呢?

函數調用方面的改進

接收地址

如果說想讓冒泡排序函數具有排序任意類型元素的功能,那么首先,它就應該能接收任意類型元素的地址(類似于qsort中的base參數)

元素個數

函數需要知道要排序多少個元素,所以就需要傳入數組的大小(類似num參數)

元素大小(寬度)

知道了待排序數組的起始位置和元素個數后,我們需要對數組中的元素進行移動操作, 那么我們就需要知道元素的大小是什么

簡易版框架如下:

void bubble_sort(void* base, int sz, int width)
{int i = 0;//次數for (i = 0; i < sz; i++){//每次需要比較的元素對數int j = 0;for (j = 0; j < sz - 1 - i; j++)//比較兩個元素{}}
}

疑問1:

基本框架搭建好了,那我們要如何比較兩個元素呢,我們又不知道他們的類型?

所以我們在傳參的時候,還需要將兩個元素的比較方法(函數)一并傳進bubble_sort函數中,也就是第四個參數

首先,要傳入的肯定是函數的地址,
其次,我們需要返回一個值來告訴我們比較的結果是什么(此處類似qsort函數的返回值)
最后,對于要比較的兩個元素,因為要求函數具有通用性,所以參數類型就是void *類型

代碼如下:

void bubble_sort(void* base, int sz, int width, int(*cmp)(void* e1,void* e2) )
{int i = 0;//次數for (i = 0; i < sz; i++){//每次需要比較的元素對數int j = 0;for (j = 0; j < sz - 1 - i; j++)//比較兩個元素{if(cmp()>0)//交換if(cmp()>0)//交換{}}}}
}

疑問2:if語句

if(cmp()>0)//交換{}

我們知道這個語句是比較兩個元素,那我們怎么找到這倆個元素呢?

我們知道,base就是首元素的地址,

想法1

那么有人想通過加減整數來找到后面的元素,這個問題在我前面的文章提到過:因為元素是void*類型的,不知道元素大小,無法與整數進行運算

想法2

那么又有人想:將base傳換成(int*)類型再運算不就行了嗎,
還是不對,因為我們不知道傳進來的參數究竟是什么類型,所以我們不能假定他的類型

想法3

小明這時候提出來:我們已經知道了每個元素的大小:width,那可不可以先把base轉換成char*類型,再加上每個元素的字節大小width呢?

這么做就可以了,
因為char*大小是一個字節,每次移動width個字節,就進入到下一個元素中了

if語句代碼如下:

if(cmp((char*)base+ j*width, (char*)base +(j+1)*width)>0)//交換
{}

疑問3:怎么交換

這里先創建一個swap函數用于兩個元素的交換

void Swap(char*buf1, char*buf2)
{}void bubble_sort(void* base, int sz, int width)
{int i = 0;//次數for (i = 0; i < sz; i++){//每次需要比較的元素對數int j = 0;for (j = 0; j < sz - 1 - i; j++)//比較兩個元素{if(cmp((char*)base+ j*width, (char*)base +(j+1)*width)>0)//交換{Swap((char*)base + j * width, (char*)base + (j + 1) * width);}}}
}

但是,我們仔細看,Swap函數接收的參數類型是char*,一個字節大小,如果我這個元素是8個字節類型,要怎么交換呢,
所以如果按照一個字節一個字節這么交換的方式,我們就需要知道要交換的元素的字節大小(width),以及要交換幾次

代碼如下:

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++;}
}

簡單實現(int類型)

完整代碼如下:

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 bubble_sort(void* base, int sz, int width, int (*cmp)(void*e1, void*e2))
{int i = 0;//次數for (i = 0; i < sz; i++){//每次需要比較的元素對數int j = 0;for (j = 0; j < sz - 1 - i; j++)//比較兩個元素{if(cmp((char*)base+ j*width, (char*)base +(j+1)*width)>0)//交換{Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}void test4()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}int main()
{test4();return 0;
}

想要進行其他類型的比較只需要在調用bubble_sort函數時,將第四個參數修改成對應的比較方法即可(當然,這需要自己構建)

小提示:
->的優先級高于強制類型轉換,所以要用()先將強制類型轉換括起來,先轉換,再訪問

題外話

因為想要使bubble_sort函數具有通用性,所以我們需要將不同類型元素的比較方法的函數的地址傳進來(也就是第四個參數),而這種將函數地址傳進另一個函數,由這個函數去實現調用的方法,就稱為回調函數(大概就是這個意思),我這幾天在整理指針的知識,有時間就寫一篇博客。

結語

沒想到感覺沒怎么寫,就寫了六千多字(捂臉)
只能再一次感嘆C的豐富

文章到這里就結束了,希望這篇文章對你有所幫助,我們下篇文章見~

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

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

相關文章

面試熱題(最大子數組和)

給你一個整數數組 nums &#xff0c;請你找出一個具有最大和的連續子數組&#xff08;子數組最少包含一個元素&#xff09;&#xff0c;返回其最大和。 子數組 是數組中的一個連續部分。 輸入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 輸出&#xff1a;6 解釋&#xff1a;連續…

免費批量ppt轉pdf?一個方法教你完美轉換

隨著科技的不斷發展&#xff0c;電子文檔的使用越來越普遍。在商業、教育和個人領域&#xff0c;我們經常需要將PPT文件轉換為PDF格式&#xff0c;以便更方便地共享和存檔。幸運的是&#xff0c;現在有許多在線工具和軟件可以幫助我們輕松地完成免費批量ppt轉pdf。下面將介紹一…

【Linux】模擬實現linux的shell

#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/wait.h> #include <sys/types.h> #define NUM 1024 #define SIZE 32 #define SEP " " int main() {//保存輸入后的字符串char …

Blazor前后端框架Known-V1.2.12

V1.2.12 Known是基于C#和Blazor開發的前后端分離快速開發框架&#xff0c;開箱即用&#xff0c;跨平臺&#xff0c;一處代碼&#xff0c;多處運行。 Gitee&#xff1a; https://gitee.com/known/KnownGithub&#xff1a;https://github.com/known/Known 概述 基于C#和Blazo…

大文件切片上傳

創建組件&#xff1a;創建一個組件用于處理文件上傳&#xff0c;命名為Upload.vue。 <template><div><input type"file" change"handleFileChange" /><button click"startUpload">開始上傳</button></div> …

Pyinstaller 打包 django 項目如何將命令行參數加入?

起因 Pyinstaller 打包 django 項目&#xff0c;打包成 manage.exe 后用命令行 cmd manage.exe runserver 0.0.0.0:8001 --noreload 來運行感覺很不方便。 希望能夠直接把命令行參數也打包進去&#xff0c;直接運行 exe 。我走了些彎路&#xff0c;但最終實現了。 彎路 我看…

Redis之刪除策略

文章目錄 前言一、過期數據二、數據刪除策略2.1定時刪除2.2惰性刪除2.3 定期刪除2.4 刪除策略比對 三、逐出算法3.1影響數據逐出的相關配置 總結 前言 Redis的常用刪除策略 一、過期數據 Redis是一種內存級數據庫&#xff0c;所有數據均存放在內存中&#xff0c;內存中的數據可…

web基礎入門和PHP語言基礎入門 一

web基礎入門和php語言基礎入門 一 WEB簡介與HTTP入門WEB簡介HTTP 簡介HTTP 請求報文&#xff1a;請求方法&#xff1a;請求頭部&#xff1a;&#xff08;常見的請求頭&#xff09;HTTP 響應報文&#xff1a;響應狀態碼&#xff1a;Cookie HTML入門學習什么是HTML什么是標記語言…

【深入了解pytorch】PyTorch擴展:如何使用PyTorch的擴展功能

【深入了解pytorch】PyTorch擴展:如何使用PyTorch的擴展功能 PyTorch擴展:展示如何使用PyTorch的擴展功能1. 自定義損失函數2. 自定義數據加載器3. 自定義優化器總結PyTorch擴展:展示如何使用PyTorch的擴展功能 PyTorch作為一個開源的深度學習框架,在研究和應用領域廣受歡…

PHP入門基礎教程 - 專欄導讀

&#x1f3c6;作者簡介&#xff0c;黑夜開發者&#xff0c;全棧領域新星創作者?&#xff0c;CSDN博客專家&#xff0c;阿里云社區專家博主&#xff0c;2023年6月CSDN上海賽道top4。 &#x1f3c6;數年電商行業從業經驗&#xff0c;歷任核心研發工程師&#xff0c;項目技術負責…

【LeetCode 算法】Find And Replace in String 字符串中的查找與替換-線性模擬

文章目錄 Find And Replace in String 字符串中的查找與替換問題描述&#xff1a;分析代碼線性模擬 Tag Find And Replace in String 字符串中的查找與替換 問題描述&#xff1a; 你會得到一個字符串 s (索引從 0 開始)&#xff0c;你必須對它執行 k 個替換操作。替換操作以三…

Floyd算法

正如我們所知道的&#xff0c;Floyd算法用于求最短路徑。Floyd算法可以說是Warshall算法的擴展&#xff0c;三個for循環就可以解決問題&#xff0c;所以它的時間復雜度為O(n^3)。 Floyd算法的基本思想如下&#xff1a;從任意節點A到任意節點B的最短路徑不外乎2種可能&#xff…

openGauss學習筆記-42 openGauss 高級數據管理-觸發器

文章目錄 openGauss學習筆記-42 openGauss 高級數據管理-觸發器42.1 語法格式42.2 參數說明42.3 示例 openGauss學習筆記-42 openGauss 高級數據管理-觸發器 觸發器會在指定的數據庫事件發生時自動執行函數。 42.1 語法格式 創建觸發器 CREATE TRIGGER trigger_name { BEFORE…

Swagger-ui在idea中的使用

1.添加依賴 <!--添加swagger2相關概念--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!--添加swagger-ui相關功能--><de…

Linux學習之基本指令一

在學習Linux下的基本指令之前首先大家要知道Linux下一切皆目錄&#xff0c;我們的操作基本上也都是對目錄的操作&#xff0c;這里我們可以聯想我們是如何在windows上是如何操作的&#xff0c;只是形式上不同&#xff0c;類比學習更容易理解。 目錄 01.ls指令 02. pwd命令 0…

SpringBoot登錄、退出、獲取用戶信息的session處理

1、登錄方法&#xff1a;login PostMapping("/user/login")public ResponseVo<User> login(Valid RequestBody UserLoginForm userLoginForm,HttpSession session) {ResponseVo<User> userResponseVo userService.login(userLoginForm.getUsername(), …

sql A表(含有部分B表字段) 向B表插入A表數據

今天遇到一個數據庫插入問題 向表中插入 生產狀態 為 2 的數據 但生產狀態為改為12 的所有數據 查看網上的評論 參考 insert into b (a,b,c) select ‘1’,‘2’,c from a where a1 這樣就可以a,b字段是插入指定某個值,而C字段則用表a的c字段. 最后解決了。忽然想起原來也有這…

實現Python對.json文件內容的讀取和寫入

要實現Python對.json文件內容的讀取和寫入&#xff0c;可以使用json庫。 首先&#xff0c;需要安裝json庫&#xff1a; pip install json 然后&#xff0c;可以編寫以下代碼來實現對.json文件內容的讀取和寫入&#xff1a; import json# 讀取json文件 with open(data.json, …

PS實現多個圖片轉化GIF動畫

PS實現多個圖片轉化為GIF動畫步驟 一、導入圖片素材1.打開PS軟件&#xff0c;點擊 [文件] --- [腳本] ---[將文件載入堆棧]2.選擇圖片3.導入成功 二、打開時間軸1.點擊[窗口]---[時間軸]2.選擇創建幀動畫3.創建幀動畫 三、創建動畫1.復制幀。2.設置幀的內容。3.修改圖片停留的時…

分布式應用:Zabbix監控Tomcat

目錄 一、理論 1.Zabbix監控Tomcat 二、實驗 1.Zabbix監控Tomcat 三、問題 1.獲取軟件包失敗 2.tomcat 配置 JMX remote monitor不生效 3.Zabbix客戶端日志報錯 一、理論 1.Zabbix監控Tomcat &#xff08;1&#xff09;環境 zabbix服務端&#xff1a;192.168.204.214 …