在 C 語言中,要編寫一個同時能處理整數陣列、字串陣列甚至自訂結構體的排序函數,關鍵在於「將比較邏輯與排序演算法分離」。這正是回調函數(Callback Function)大放異彩的場合。本文將帶你一步步實作一個通用的冒泡排序,並深入剖析其中的指標運算與記憶體模型。
通用排序函數需要四個關鍵資訊:
void *base):資料陣列的起始位址int count):待排序項目數量int size):單一元素佔用的位元組數int (*cmp)(const void*, const void*)):回調函數指標,決定排序順序void swap(void *p1, void *p2, int size) {
for (int i = 0; i < size; i++) {
char tmp = *((char *)p1 + i);
*((char *)p1 + i) = *((char *)p2 + i);
*((char *)p2 + i) = tmp;
}
}
以 char* 逐位元組搬移,不受資料型別限制,完美支援任意大小的結構體。
void bubble(void *base, int count, int size, int(*cmp)(const void *, const void *)) {
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - i - 1; j++) {
// 計算第 j 和 j+1 個元素的位址
void *elem1 = (char *)base + size * j;
void *elem2 = (char *)base + size * (j + 1);
if (cmp(elem1, elem2) > 0) {
swap(elem1, elem2, size);
}
}
}
}
位址運算 (char *)base + size * j 是精髓:將 void* 強轉為 char* 後,每遞增 1 即前進 1 位元組,配合 size 即可精準定位任意型別元素。
int int_cmp(const void *p1, const void *p2) {
int v1 = *(const int *)p1;
int v2 = *(const int *)p2;
return (v1 > v2) - (v1 < v2); // 返回 1/0/-1
}
int str_cmp(const void *p1, const void *p2) {
// 陣列元素是 char*,傳入的是 char** 型別
const char *s1 = *(const char **)p1;
const char *s2 = *(const char **)p2;
return strcmp(s1, s2);
}
注意:字串陣列 char *arr[] 在記憶體中儲存的是指標,因此比較函數需雙重解參考。
#include <stdio.h>
#include <string.h>
// ... 上述 swap、bubble、int_cmp、str_cmp 實作 ...
int main() {
// 測試整數排序
int nums[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int n = sizeof(nums) / sizeof(nums[0]);
bubble(nums, n, sizeof(int), int_cmp);
puts("整數排序結果:");
for (int i = 0; i < n; i++) printf("%d ", nums[i]);
putchar('\n');
// 測試字串排序
char *strs[] = { "dddd", "bbbb", "cccc", "aaaa" };
int m = sizeof(strs) / sizeof(strs[0]);
bubble(strs, m, sizeof(char *), str_cmp);
puts("字串排序結果:");
for (int i = 0; i < m; i++) printf("%s\n", strs[i]);
return 0;
}
整數排序結果: 0 1 2 3 4 5 6 7 8 9 字串排序結果: aaaa bbbb cccc dddd
C 標準庫的 qsort 正是採用相同介面設計(基地址、個數、大小、比較函數),但內部採用快速排序,平均時間複雜度 O(n log n),遠優於冒泡的 O(n²)。理解本文機制後,閱讀 qsort 原始碼將毫無壓力。
int (*)(const void*, const void*),否則會產生未定義行為。sizeof(char*) 而非 sizeof(char[])。const StructType* 存取成員。const,保證不修改原始資料。回調函數模式實現了「演算法與資料分離」,是 C 語言實現多態的核心手法。掌握 void* 指標運算、函數指標語法與記憶體佈局,即可編寫出像 qsort、bsearch 這樣的通用工具函數,這正是從 C 初學者邁向系統級開發者的關鍵門檻。