前幾天在leetcode上刷題,用qsort對二維數組進行排序,發現不會寫qsort的比較函數。后面在網上找了幾篇博客才弄明白,趁今天有空,對這個做一下總結,主要是以下4個方面:
1、qsort總體介紹
2、qsort應用於一維數組
3、qsort應用於指針數組
4、qsort應用於二維數組
1、qsort總體介紹
函數聲明:void qsort(void *base, size_t nitems, size_t size, int (*compare)(const void *, const void*))
參數解釋:
base-- 指向要排序的數組的第一個元素的指針。注意這里說是數組,所以必須是對連續的內存塊進行排序。
nitems-- 由 base 指向的數組中元素的個數
size-- 數組中每個元素的大小,以字節為單位
compare-- 用來比較兩個元素的函數。這是qsort最難的一部分了。這里主要要注意以下2點:1、在寫compare函數時,你的兩個形參必須是const void *型,但是在compare函數內部你又必須將const void *類型的形參轉換為實際的類型。這是我最當時最難以理解的一個問題了:我需要轉換成什么類型。看了別人的總結才知道,是:指向數組元素的指針,指向數組元素的指針,指向數組元素的指針。重要的事情說三遍。具體怎么理解,會在下面的例題中詳細講,我認為這時理解qsort的一個綱領。2、comapre函數的返回值是int,即整型。記住這一點,當你在比較double類型時就不會出錯了。如果返回的是一個正數。說明第一個參數排在第二個參數的后面,如果是一個負數,說明第一個參數排在第二個參數的前面。如果等於零,說明它們兩是相等的,誰先誰后都沒關系。
2、qsort應用於一維數組
2.1、一維整型數組的用法
舉例:

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int compare(const void *p1, const void *p2) { 5 6 return (*(int *)p1) - (*(int *)p2); // 升序排序 7 //return (*(int *)p2) - (*(int *)p1); // 降序排序 8 } 9 10 int main(int argc, char** argv) 11 { 12 int i; 13 int arr[5] = { 13, 17, 2, 7, 71 }; 14 printf("Before qsort: "); 15 for (i = 0; i < 5; i++) { 16 printf("%d ", arr[i]); 17 } 18 printf("\n"); 19 20 qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), compare); 21 printf("After qsort: "); 22 for (i = 0; i < 5; i++) { 23 printf("%d ", arr[i]); 24 } 25 printf("\n"); 26 return 0; 27 }
運行結果:
解釋一下 qsort的每一個參數。qsort第一個參數是指向待排序的數組的第一個元素的指針。由於數組名作為函數的參數時,那么數組名立刻就會轉化為指向該數組第一個元素的指針。因此,第一個參數我們寫的是數組名arr。第二個參數是數組元素的個數,所以可以寫成:sizeof(arr)/sizeof(arr[0])。第三個參數是每個元素的大小,所以可以寫成 sizeof(arr[0])。我們重點來看第四個參數,即compare函數。首先compare函數的入參都寫成了const void*。其次,在函數中,我們需要把入參p1,p2轉換成實際的類型。什么類型? 依據綱領:指向數組元素的指針。我們的數組元素是什么?一個int(整型)。那么指向int的指針自然就是int*了。所以p1,p2在compare函數內部轉換為了 (int *)p1, (int *)p2。然后再取解引用對int進行比較。
2.2、一維double型數組的用法
舉例:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #define ARRAY_LEN 6 4 5 int compare1(const void *p1, const void *p2) { 6 return (*(double *)p1) - (*(double *)p2); 7 } 8 9 int main(int argc, char** argv) 10 { 11 int i; 12 double arr1[ARRAY_LEN] = {1.2, -1.2, 1.3, 33.3, 44.5, 77.5}; 13 printf("Before qsort: "); 14 for (i = 0; i < ARRAY_LEN; i++) { 15 printf("%.1f ", arr1[i]); 16 } 17 printf("\n"); 18 19 qsort(arr1, sizeof(arr1)/sizeof(arr1[0]), sizeof(arr1[0]), compare1); 20 printf("After qsort: "); 21 for (i = 0; i < ARRAY_LEN; i++) { 22 printf("%.1f ", arr1[i]); 23 } 24 printf("\n"); 25 return 0; 26 }
我以為這個程序會正確排序,但結果卻啪啪打臉:
what? 1.3 比 1.2要小?我讀書雖然少,但1.3比1.2大這個常識,我還是知道的。那到底哪里出錯了?沒錯,就是比較函數compare1的返回值寫錯了。比較函數的返回值類型是啥? 整型。int型。但是你看compare1中返回值都寫的是啥?我返回的是兩個double類型相減的類型,還是double型。這樣返回的double類型就會截斷(假如我返回的是 -0.1,截斷后返回的就是0了,就是說1.2和1.3它們誰先誰后都沒關系,那我當然是啥都不做方便啊,自然1.2就放前面了)。返回的結果就不確定了,導致出現這個錯誤。
下面是修改之后的程序:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #define ARRAY_LEN 6 4 5 int compare1(const void *p1, const void *p2) { 6 return ((*(double *)p1) > (*(double *)p2)) ? 1 : -1; 7 } 8 9 int main(int argc, char** argv) 10 { 11 int i; 12 double arr1[ARRAY_LEN] = {1.2, -1.2, 1.3, 33.3, 44.5, 77.5}; 13 printf("Before qsort: "); 14 for (i = 0; i < ARRAY_LEN; i++) { 15 printf("%.1f ", arr1[i]); 16 } 17 printf("\n"); 18 19 qsort(arr1, sizeof(arr1)/sizeof(arr1[0]), sizeof(arr1[0]), compare1); 20 printf("After qsort: "); 21 for (i = 0; i < ARRAY_LEN; i++) { 22 printf("%.1f ", arr1[i]); 23 } 24 printf("\n"); 25 return 0; 26 }
運行結果:
2.3、一維字符數組
這個沒啥好說的,和一維整型數組的用法類似。直接看一個例題吧:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int compare2(const void *p1, const void *p2) { 6 return (*(char *)p1) - (*(char *)p2); 7 } 8 9 int main(int argc, char** argv) 10 { 11 char c[] = {'c', 'd', 'a', 'A', 'f', 'B', 'w', 'v', 'W', 'V'}; 12 printf("Before qsort: "); 13 for (int i = 0; i < sizeof(c) / sizeof(c[0]); i++) { 14 printf("%c ", c[i]); 15 } 16 printf("\n"); 17 18 qsort(c, sizeof(c) / sizeof(c[0]), sizeof(c[0]), compare2); 19 printf("After qsort: "); 20 for (int i = 0; i < sizeof(c) / sizeof(c[0]); i++) { 21 printf("%c ", c[i]); 22 } 23 printf("\n"); 24 return 0; 25 }
運行結果:
3、qsort應用於指針數組
例題:

1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 int compare(const void *arg1, const void *arg2) { 6 char *a = *(char**)arg1; 7 char *b = *(char**)arg2; 8 return strcmp(a, b); 9 } 10 11 int main() 12 { 13 int i; 14 char *arr[5] = { "i", "love", "c", "programming", "language" }; 15 16 qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(char *), compare); 17 for (i = 0; i < 5; i++) { 18 printf("%s ", arr[i]); 19 } 20 printf("\n"); 21 return 0; 22 }
我們還是來分析一下為什么arg1的類型是 char **。根據綱領:指向數組元素的指針。我們首先需要確定數組元素的類型。這里數組元素的類型是是 char *。所以,arg1的類型就是指向char*的指針,即char**。
運行結果:
4、qsort應用於二維數組
例題:

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 5 int cmp1(const void *arg1, const void *arg2) 6 { 7 return strcmp((*((char (*)[])arg1)), (*((char (*)[])arg2))); 8 } 9 10 int cmp2(const void *arg1, const void *arg2) 11 { 12 return strcmp((char *)arg1, (char *)arg2); 13 } 14 15 int main(int argc, char **argv) 16 { 17 int i; 18 char str1[4][8] = {"java", "c", "python", "peal"}; 19 printf("COMPARE-FUNCTION-1: "); 20 qsort(str1, 4, sizeof(str1[0]), cmp1); 21 for (int i = 0; i < 4; i++) { 22 printf("%s ", str1[i]); 23 } 24 printf("\n"); 25 26 char str2[4][8] = {"java", "c", "python", "peal"}; 27 printf("COMPARE-FUNCTION-2: "); 28 qsort(str2, 4, sizeof(str2[0]), cmp2); 29 for (int i = 0; i < 4; i++) { 30 printf("%s ", str2[i]); 31 } 32 printf("\n"); 33 return 0; 34 }
這一段代碼剛開始我也是挺疑惑的,為什么compare函數的入參是這個類型的。其實只要記住qsort的綱領:指向數組元素的指針,就很容易理解了。首先來看cmp1,我們待排序的是一個二維數組,也就是數組的數組,那么我們這個數組元素的類型就是一個數組類型了(char []類型)。cmp1的參數就是指向一個數組類型的指針了。指向一個char []數組類型的指針當然就是用char (*)[]表示了。
接下來分析cmp2。我們將str2傳入qsort函數,qsort函數將str2理解為指向數組第一個元素的指針,str2的第一個元素是str2[0][0],所以參數arg1和arg2指的是指向"a[i][0]"的指針,我們知道,a[i][0]是字符,就是char,所以arg1和arg2指的是char *。
運行結果:
參考文獻:
http://www.cppblog.com/Joe/archive/2010/10/29/131746.aspx?opt=admin
https://www.cnblogs.com/nipan/p/4119714.html