C 語言雖無原生類與繼承,但通過 **結構體 + 函數指針** 可完美模擬「接口 + 實現 + 封裝」的 OO 模式。本文以泛型鏈表 List 為例,完整展示接口定義、構造/析構、方法綁定、_this 指針模擬 this、測試驗證全流程。
| OO 概念 | C 語言映射 |
|---|---|
| 接口 | 結構體包含函數指針成員 |
| 類 | 結構體類型定義 |
| 對象/實例 | 結構體變量 (堆分配) |
| this 指針 | 結構體內 _this 自引用指針 |
| 方法 | 接收 _this 為隱式參數的函數 |
| 封裝 | 靜態全局變量 + 只暴露接口頭文件 |
| 泛型 | void * 數據域 + 調用者自行類型轉換 |
IList.h
#ifndef _ILIST_H
#define _ILIST_H
typedef struct Node {
void *data;
struct Node *next;
} Node;
typedef struct List {
struct List *_this; // 模擬 this 指針
Node *head; // 虛擬頭節點
int size; // 元素計數
// 方法指針 (接口)
void (*insert)(void *node);
void (*drop)(void *node);
void (*clear)();
int (*getSize)();
void* (*get)(int index);
void (*print)();
} List;
// 構造/析構函數聲明
List *ListConstruction();
void ListDeconstruction();
#endif
List.c 關鍵片段
#include
#include
#include "IList.h"
static Node *node = NULL; // 文件級靜態,模擬私有成員
static List *list = NULL; // 單例模式 (演示用),實際應支持多實例
// 內部輔助函數聲明
static void insert(void *node);
static void drop(void *node);
static void clear();
static int getSize();
static void* get(int index);
static void print();
// 構造函數:分配內存、初始化字段、綁定方法指針、設置 _this
List *ListConstruction() {
list = (List*)malloc(sizeof(List));
node = (Node*)malloc(sizeof(Node)); // 虛擬頭節點
list->head = node;
list->size = 0;
list->_this = list; // 關鍵:自我引用
// 綁定方法
list->insert = insert;
list->drop = drop;
list->clear = clear;
list->getSize = getSize;
list->get = get;
list->print = print;
return list;
}
// 插入:頭插法 (虛擬頭節點後)
void insert(void *data) {
Node *cur = (Node*)malloc(sizeof(Node));
cur->data = data;
cur->next = list->_this->head->next;
list->_this->head->next = cur;
(list->_this->size)++;
}
// 刪除:按數據指釖比較查找並刪除
void drop(void *data) {
Node *prev = list->_this->head;
Node *curr = prev->next;
while (curr) {
if (curr->data == data) { // 指針比較,調用者確保同一對象
prev->next = curr->next;
free(curr);
(list->_this->size)--;
break;
}
prev = curr;
curr = curr->next;
}
}
// 獲取指定索引元素 (0-based)
void* get(int index) {
if (index < 0 || index >= list->_this->size) return NULL;
Node *cur = list->_this->head->next;
for (int i = 0; i < index; ++i) cur = cur->next;
return cur->data;
}
// 清空並釋放所有節點
void clear() {
Node *cur = list->_this->head->next;
while (cur) {
Node *next = cur->next;
free(cur);
cur = next;
}
list->_this->head->next = NULL;
list->_this->size = 0;
// 注意:list 結構體自身不在此釋放,由 ListDeconstruction 處理
}
void print() {
Node *cur = list->_this->head->next;
int i = 0;
while (cur) {
printf("[%p] = {%s}\n", (void*)&cur->data, (char*)cur->data);
cur = cur->next;
}
}
Main.c
#include
#include "IList.h"
int main() {
List *list = ListConstruction();
// 插入字符串字面量 (存儲在只讀區,生命周期全程有效)
list->insert("Apple");
list->insert("Borland");
list->insert("Cisco");
list->insert("Dell");
list->insert("Electrolux");
list->insert("FireFox");
list->insert("Google");
printf("初始列表:\n");
list->print();
printf("Size: %d\n\n", list->getSize());
// 刪除 "Electrolux" 和 "Cisco"
// 注意:需傳遞與插入時完全相同的指針值
Node node;
node.data = "Electrolux"; node.next = NULL;
list->drop(&node);
node.data = "Cisco"; node.next = NULL;
list->drop(&node);
printf("刪除後:\n");
list->print();
printf("Size: %d\n", list->getSize());
list->clear();
free(list); // 釋放 List 結構體
return 0;
}
初始列表:
[00489760] = {Google}
[00489730] = {FireFox}
[00489700] = {Electrolux}
[004896D0] = {Dell}
[004896A0] = {Cisco}
[00489670] = {Borland}
[00489640] = {Apple}
Size: 7
刪除後:
[00489760] = {Google}
[00489730] = {FireFox}
[004896D0] = {Dell}
[00489670] = {Borland}
[00489640] = {Apple}
Size: 5
_this 自引用指針:方法內部通過 list->_this->head 訪問對象自身數據,模擬 C++ this->head。構造時必須 list->_this = list。static List *list 對外不可見,只能通過接口函數訪問,實現信息隱藏。void *:數據域類型擦除,調用者負責類型安全 (如 (char*)list->get(i))。list,構造函數返回新分配對象,方法接收 List *self 參數。將上文 Executor (函數指針封裝任務) 放入此 List,添加同步機制 (互斥鎖/條件變量) 即可構建簡易線程池:
List 作任務隊列drop 取任務執行insert 提交新任務C 語言「結構體 + 函數指針 + _this 自引用」三板斧,打通了面向對象設計思想在過程式語言中的落地路徑,是理解 C++ 編譯器底層實現 (vtable、this 指針調整) 的最佳切入點。