C 語言無模板、無虛函式,若要寫出像 C++ STL 容器一樣支援任意型別的通用資料結構,核心技術是 void* 指標 + 函式指標。本文以循環佇列為例,示範如何將比較、列印、查找等型別相關操作抽象為回調函式,實現真正的型別無關容器。
typedef struct Queue {
int front, rear; // 隊頭、隊尾索引
int capacity, size; // 容量、當前元素數
void **data; // 元素指標陣列(void* 陣列)
// 型別相關操作回調
int (*compare)(const void*, const void*); // 比較:<0, =0, >0
void (*print)(const void*); // 列印單一元素
void* (*find_key)(const void*, const void*); // 查找:回傳匹配元素指標
void (*free_elem)(void*); // 釋放元素記憶體(可選)
} Queue;
Queue* queue_create(int capacity,
int (*cmp)(const void*, const void*),
void (*prn)(const void*),
void* (*fnd)(const void*, const void*),
void (*fre)(void*)) {
if (capacity <= 0) return NULL;
Queue *q = (Queue*)malloc(sizeof(Queue));
if (!q) return NULL;
q->data = (void**)malloc(capacity * sizeof(void*));
if (!q->data) { free(q); return NULL; }
q->front = q->rear = q->size = 0;
q->capacity = capacity;
q->compare = cmp;
q->print = prn;
q->find_key = fnd;
q->free_elem = fre;
return q;
}
void queue_destroy(Queue *q) {
if (!q) return;
if (q->free_elem) {
for (int i = 0; i < q->size; i++) {
int idx = (q->front + i) % q->capacity;
q->free_elem(q->data[idx]);
}
}
free(q->data);
free(q);
}
int queue_push(Queue *q, void *elem) {
if (!q || q->size == q->capacity) return -1; // 滿
q->data[q->rear] = elem;
q->rear = (q->rear + 1) % q->capacity;
q->size++;
return 0;
}
void* queue_pop(Queue *q) {
if (!q || q->size == 0) return NULL;
void *elem = q->data[q->front];
q->front = (q->front + 1) % q->capacity;
q->size--;
return elem; // 呼叫端負責釋放(除非佇列擁有所有權)
}
void queue_print(const Queue *q) {
if (!q || q->size == 0 || !q->print) return;
for (int i = 0; i < q->size; i++) {
int idx = (q->front + i) % q->capacity;
q->print(q->data[idx]);
}
putchar('\n');
}
void* queue_find(const Queue *q, const void *key) {
if (!q || !q->find_key) return NULL;
for (int i = 0; i < q->size; i++) {
int idx = (q->front + i) % q->capacity;
void *found = q->find_key(q->data[idx], key);
if (found) return found;
}
return NULL;
}
int int_cmp(const void *a, const void *b) {
int ia = *(const int*)a, ib = *(const int*)b;
return (ia > ib) - (ia < ib);
}
void int_print(const void *a) {
printf("%d ", *(const int*)a);
}
void* int_find(const void *elem, const void *key) {
return (*(const int*)elem == *(const int*)key) ? (void*)elem : NULL;
}
void int_free(void *elem) { free(elem); }
// 使用示範
int main() {
Queue *q = queue_create(10, int_cmp, int_print, int_find, int_free);
for (int i = 1; i <= 5; i++) {
int *val = (int*)malloc(sizeof(int));
*val = i * 10;
queue_push(q, val);
}
queue_print(q); // 輸出: 10 20 30 40 50
int key = 30;
int *found = (int*)queue_find(q, &key);
if (found) printf("找到: %d\n", *found);
queue_destroy(q);
return 0;
}
QueueIter 結構,支援 next()、get(),算法與容器解耦void* (*alloc)(size_t)、void (*dealloc)(void*),支援記憶體池TypeInfo 結構,建立型別註冊表,避免重複傳遞函式指標queue_sort、queue_filter、queue_map 等高階函式C++ 模板在編譯期單態化產生型別專用代碼,零開銷;C 函式指標在執行期間接呼叫,有微小效能成本,但二進位碼體積更小、編譯更快、支援動態載入型別。兩者各有取捨,理解其本質差異有助於架構決策。
通用資料結構的精髓在於「將變化點(型別操作)參數化」。以 void* 擦除型別、以函式指標注入行為,是 C 語言實現多態的標準模式。掌握此模式,即可構建出如 GLib、Linux 核心 linked list 等工業級通用容器庫。