雖然C語言不原生支持面向對象特性,但通過結構體、函數指針與虛表機制,可完整實現繼承與多態。本文從類的C語言表示開始,逐步構建支持虛函數、動態綁定、正確析構的完整體系。
// Person.h
typedef struct _Person {
char* pFirstName;
char* pLastName;
} Person;
Person* new_Person(const char* first, const char* last); // 構造函數
void delete_Person(Person* p); // 析構函數
void Person_DisplayInfo(Person* p);
void Person_WriteToFile(Person* p, const char* filename);
缺點:無封裝性,調用者需顯式傳遞this指針,無法實現多態。
// Person.h 進階版
typedef struct _Person Person;
typedef void (*fptrDisplayInfo)(Person*);
typedef void (*fptrWriteToFile)(Person*, const char*);
typedef void (*fptrDelete)(Person*);
struct _Person {
char* pFirstName;
char* pLastName;
// 虛表接口
fptrDisplayInfo Display;
fptrWriteToFile WriteToFile;
fptrDelete Delete;
};
// 構造函數初始化虛表
Person* new_Person(const char* first, const char* last) {
Person* pObj = malloc(sizeof(Person));
pObj->pFirstName = strdup(first);
pObj->pLastName = strdup(last);
// 綁定基類實現
pObj->Display = Person_DisplayInfo;
pObj->WriteToFile = Person_WriteToFile;
pObj->Delete = delete_Person;
return pObj;
}
調用方式變為面向對象風格:
Person* p = new_Person("Anjali", "Jaiswal");
p->Display(p); // 相當於 p->vptr->Display(p)
p->WriteToFile(p, "data.txt");
p->Delete(p);
// 基類 Person
struct _Person {
void* pDerivedObj; // 指向派生對象(關鍵:基類持有派生引用)
char* pFirstName;
char* pLastName;
fptrDisplayInfo Display;
fptrWriteToFile WriteToFile;
fptrDelete Delete;
};
// 派生類 Employee
struct _Employee {
Person* pBaseObj; // 指向基類對象(派生類持有基類引用)
char* pDepartment;
char* pCompany;
int nSalary;
};
雙向引用設計意圖:
pDerivedObj:多態調用時訪問派生類數據pBaseObj:構造/析構鏈調用基類邏輯Person* new_Employee(const char* first, const char* last,
const char* dept, const char* comp, int salary) {
// 1. 調用基類構造函數
Person* pObj = new_Person(first, last);
// 2. 分配派生類內存
Employee* pEmp = malloc(sizeof(Employee));
pEmp->pBaseObj = pObj; // 派生引用基類
pObj->pDerivedObj = pEmp; // 基類引用派生(關鍵步驟)
// 3. 初始化派生類成員
pEmp->pDepartment = strdup(dept);
pEmp->pCompany = strdup(comp);
pEmp->nSalary = salary;
// 4. 覆寫虛表指針(實現多態核心)
pObj->Display = Employee_DisplayInfo;
pObj->WriteToFile = Employee_WriteToFile;
pObj->Delete = delete_Employee; // 虛析構函數
return pObj;
}
void Employee_DisplayInfo(Person* pPersonObj) {
// 關鍵:從基類對象獲取派生類對象
Employee* pEmp = (Employee*)pPersonObj->pDerivedObj;
printf("Employee: %s %s, Dept: %s, Company: %s, Salary: %d\n",
pEmp->pBaseObj->pFirstName, pEmp->pBaseObj->pLastName,
pEmp->pDepartment, pEmp->pCompany, pEmp->nSalary);
}
void delete_Employee(Person* pPersonObj) {
Employee* pEmp = (Employee*)pPersonObj->pDerivedObj;
// 先釋放派生類資源
free(pEmp->pDepartment);
free(pEmp->pCompany);
free(pEmp);
// 再釋放基類資源(基類析構會處理基類成員)
delete_Person(pPersonObj);
}
int main() {
Person* person = new_Person("Anjali", "Jaiswal");
Person* employee = new_Employee("Gauri", "Jaiswal", "HR", "TCS", 40000);
// 統一接口調用,實際執行不同實現
person->Display(person); // 輸出 Person 信息
employee->Display(employee); // 輸出 Employee 信息(多態!)
person->WriteToFile(person, "person.txt");
employee->WriteToFile(employee, "employee.txt");
// 虛析構確保正確清理
person->Delete(person);
employee->Delete(employee);
return 0;
}
| 機制 | C++實現 | C語言模擬 |
|---|---|---|
| 虛函數表 | 編譯器自動生成 | 結構體內嵌函數指針數組 |
| this指針 | 隱式傳遞 | 顯式作為第一參數傳遞 |
| 繼承 | 語法原生支持 | 組合 + 雙向引用 |
| 虛析構 | 關鍵字virtual | 析構函數指針指向派生類析構 |
| 動態綁定 | 運行時查虛表 | 運行時通過函數指針間接調用 |
pDerivedObj位於基類結構體首部,便於類型轉換dynamic_cast類功能通過「虛表指針 + 雙向引用 + 函數指針重寫」三大核心技術,C語言可實現完整的繼承與多態機制。這不僅是面試熱門考點,更是理解C++對象模型、設計插件架構、實現跨語言互操作的基礎能力。掌握這套機制,意味著你真正理解了面向對象在底層的運行本質。