《Head First C 中文版》審讀筆記(二)

黃志斌發表於2013-05-04

《Head First C 中文版》第 327 頁:

假設有一個整型陣列,你想升序排列它們,比較器函式應該長什麼樣子?

int scores[] = { 543, 323, 32, 554, 11, 3, 112 }; 

觀察 qsort() 接收的比較器函式的簽名,會發現它接收兩個 void*,也就是兩個 void 指標。我們在使用 malloc() 時碰到過它,void 指標可以儲存任何型別資料的地址,但使用前必須把它轉換為特定的型別。

qsort() 函式兩兩比較陣列元素,然後以正確的順序排列它們。qsort() 通過呼叫傳給它的比較器函式來比較兩個元素的大小。

int compare_scores(const void* score_a, const void* score_b)
{
  ...
}

值以指標的形式傳給函式,因此要做的第一件事就是從指標中提取整型值。

int a = *(int*)score_a;  // 你需要把 void 指標轉換為整型指標。
int b = *(int*)score_b;  // 第一個 * 就能得到儲存在地址 score_b 中的整型值了。

如果 a 大於 b,需要返回正數;如果 a 小於 b,就返回負數;如果相等,返回 0 值。對整型來講這很簡單,只要將兩數相減就行了:

return a - b;  // 如果 a > b,就是正數;如果 a < b,就是負數;如果 a、b 相等,就是 0 。

下面是用 qsort() 排序這個陣列的方法:

qsort(scores, 7, sizeof(int), compare_scores);

然後,根據本書第 330~332 頁的程式碼,得到以下 test_drive.c:

 1: #include <stdio.h>
 2: #include <string.h>
 3: #include <stdlib.h>
 4: 
 5: // 升序排列整型得分。
 6: int compare_scores(const void *score_a, const void *score_b)
 7: {
 8:   int a = *(int *)score_a;
 9:   int b = *(int *)score_b;
10:   return a - b;
11: }
12: 
13: // 降序排列整型得分。
14: int compare_scores_desc(const void *score_a, const void *score_b)
15: {
16:   int a = *(int *)score_a;
17:   int b = *(int *)score_b;
18:   return b - a; // 如果用第二個數減第一個,可以反轉最終的排序
19: }
20: 
21: // 矩形型別。
22: typedef struct {
23:   int width;
24:   int height;
25: } rectangle;
26: 
27: // 按面積從小到大排列矩形。
28: int compare_areas(const void *a, const void *b)
29: {
30:   rectangle *ra = (rectangle *)a;
31:   rectangle *rb = (rectangle *)b;
32:   int area_a = (ra->width * ra->height);
33:   int area_b = (rb->width * rb->height);
34:   return area_a - area_b;
35: }
36: 
37: // 按字母序排列名字,區分大小寫
38: int compare_names(const void *a, const void *b)
39: {
40:   char **sa = (char **)a;
41:   char **sb = (char **)b;
42:   return strcmp(*sa, *sb);
43: }
44: 
45: // 按面積從大到小排列矩形。
46: int compare_areas_desc(const void *a, const void *b)
47: {
48:   return compare_areas(b, a); // 或者也可以寫 -compare_areas(a, b)。
49: }
50: 
51: // 按逆字母序排列名字,區分大小寫
52: int compare_names_desc(const void *a, const void *b)
53: {
54:   return compare_names(b, a); // 或者也可以寫 -compare_names(a, b)。
55: }
56: 
57: int main()
58: {
59:   int scores[] = { 543, 323, 32, 554, 11, 3, 112 };
60:   int i;
61:   qsort(scores, 7, sizeof(int), compare_scores_desc);
62:   puts("These are the scores in order:");
63:   for (i = 0; i < 7; i++) {
64:     printf("Score = %i\n", scores[i]);
65:   }
66:   char *names[] = {"Karen", "Mark", "Brett", "Molly"};
67:   qsort(names, 4, sizeof(char *), compare_names);
68:   puts("These are the names in order:");
69:   for (i = 0; i < 4; i++) {
70:     printf("%s\n", names[i]);
71:   }
72:   return 0;
73: }

編譯和執行程式碼,將得到:

> gcc test_drive.c -o test_drive && ./test_drive
These are the scores in order:
Score = 554
Score = 543
Score = 323
Score = 112
Score = 32
Score = 11
Score = 3
These are the names in order:
Brett
Karen
Mark
Molly
>

這個結果和本書中第 333 頁是一致的。但是,如果我們把上述程式碼第 59 行改為:

int scores[] = { 543, 323, 32, 554, 11, 3, -2147483648 };

再次編譯和執行程式碼,將得到:

> gcc test_drive.c -o test_drive && ./test_drive
These are the scores in order:
Score = -2147483648
Score = 554
Score = 543
Score = 323
Score = 32
Score = 11
Score = 3
These are the names in order:
Brett
Karen
Mark
Molly
> 

這個結果當然不對。這是因為上述程式碼中第 18 行的b - a運算溢位了。要得到正確的比較器函式,只需將上述程式碼中第 13~19 行的compare_scores_desc函式修改如下:

// 降序排列整型得分。
int compare_scores_desc(const void *score_a, const void *score_b)
{
  int a = *(int *)score_a;
  int b = *(int *)score_b;
  if (a > b) return -1; // 注意:是降序
  if (a < b) return 1;
  return 0;
}

再次編譯並執行程式碼,將得到:

> gcc test_drive.c -o test_drive && ./test_drive
These are the scores in order:
Score = 554
Score = 543
Score = 323
Score = 32
Score = 11
Score = 3
Score = -2147483648
These are the names in order:
Brett
Karen
Mark
Molly
> 

我們得到了正確結果。當然,就這本書討論的問題來說,不可能發生溢位的情況。

相關文章