繼承和多態是面向對象語言最強大的功能。有了繼承和多態,我們可以完成代碼重用。在C中有許多技巧可以實現多態。本文的目的就是演示一種簡單和容易的技術,在C中應用繼承和多態。通過創建一個VTable(virtual table)和在基類和派生類對象之間提供正確的訪問,我們能在C中實現繼承和多態。VTable能通過維護一張函數表指針表來實現。為了提供基類和派生類對象之間的訪問,我們可以在基類中維護派生類的引用和在派生類中維護基類的引用。
在C中實現繼承和多態之前,我們應該知道類(Class)在C中如何表示。
考慮C++中的一個類"Person"。
//Person.h
class Person
{
private:
char* pFirstName;
char* pLastName;
public:
Person(const char* pFirstName, const char* pLastName); //constructor
~Person(); //destructor
void displayInfo();
void writeToFile(const char* pFileName);
};
在C中表示上面的類,我們可以使用結構體,並用操作結構體的函數表示成員函數。
//Person.h
typedef struct _Person
{
char* pFirstName;
char* pLastName;
}Person;
new_Person(const char* const pFirstName, const char* const pLastName); //constructor
delete_Person(Person* const pPersonObj); //destructor
void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, const char* const pFileName);
這裡,定義的操作結構體Person的函數沒有封裝。為了實現封裝,即綁定數據、函數、函數指針。我們需要創建一個函數指針表。構造函數new_Person()將設置函數指針值以指向合適的函數。這個函數指針表將作為對象訪問函數的接口。
下面我們重新定義C中實現類Person。
//Person.h
typedef struct _Person Person;
//declaration of pointers to functions
typedef void (*fptrDisplayInfo)(Person*);
typedef void (*fptrWriteToFile)( Person*, const char*);
typedef void (*fptrDelete)( Person *) ;
//Note: In C all the members are by default public. We can achieve
//the data hiding (private members), but that method is tricky.
//For simplification of this article
// we are considering the data members //public only.
typedef struct _Person
{
char* pFName;
char* pLName;
//interface for function
fptrDisplayInfo Display;
fptrWriteToFile WriteToFile;
fptrDelete Delete;
}Person;
person* new_Person(const char* const pFirstName,
const char* const pLastName); //constructor
void delete_Person(Person* const pPersonObj); //destructor
void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, const char* pFileName);
new_Person()函數作為構造函數,它返回新創建的結構體實例。它初始化函數指針接口去訪問其它成員函數。這裡要注意的一點是,我們僅僅定義了那些允許公共訪問的函數指針,並沒有給定私有函數的接口。讓我們看一下new_Person()函數或C中類Person的構造函數。
//Person.c
person* new_Person(const char* const pFirstName, const char* const pLastName)
{
Person* pObj = NULL;
//allocating memory
pObj = (Person*)malloc(sizeof(Person));
if (pObj == NULL)
{
return NULL;
}
pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1));
if (pObj->pFirstName == NULL)
{
return NULL;
}
strcpy(pObj->pFirstName, pFirstName);
pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1));
if (pObj->pLastName == NULL)
{
return NULL;
}
strcpy(pObj->pLastName, pLastName);
//Initializing interface for access to functions
pObj->Delete = delete_Person;
pObj->Display = Person_DisplayInfo;
pObj->WriteToFile = Person_WriteToFile;
return pObj;
}
創建完對象之後,我們能夠訪問它的數據成員和函數。
Person* pPersonObj = new_Person("Anjali", "Jaiswal");
//displaying person info
pPersonObj->Display(pPersonObj);
//writing person info in the persondata.txt file
pPersonObj->WriteToFile(pPersonObj, "persondata.txt");
//delete the person object
pPersonObj->Delete(pPersonObj);
pPersonObj = NULL;
注意:不像C++,在C中我們不能在函數中直接訪問數據成員。在C++中,可以隱式通過“this”指針直接訪問數據成員。我們知道C中是沒有“this”指針的,通過顯示地傳遞對象給成員函數。在C中為了訪問類的數據成員,我們需要把調用對象作為函數參數傳遞。上面的例子中,我們把調用對象作為函數的第一個參數,通過這種方法,函數可以訪問對象的數據成員。
Person類的表示——檢查初始化接口指向成員函數:
繼承-Employee類繼承自Person類:

在上面的例子中,類Employee繼承類Person的屬性。因為DisplayInfo()和WriteToFile()函數是virtual的,我們能夠從Person的實例訪問Employee對象中的同名函數。為了實現這個,我們創建Person實例的時候也初始化Employee類。多態使這成為可能。 在多態的情況下,去解析函數調用,C++使用VTable——即一張函數指針表。
前面我們在結構體中維護的指向函數的指針接口的作用類似於VTable。
//Polymorphism in C++
Person PersonObj("Anjali", "Jaiswal");
Employee EmployeeObj("Gauri", "Jaiswal", "HR", "TCS", 40000);
Person* ptrPersonObj = NULL;
//preson pointer pointing to person object
ptrPersonObj = &PersonObj;
//displaying person info
ptrPersonObj ->Display();
//writing person info in the persondata.txt file
ptrPersonObj ->WriteToFile("persondata.txt");
//preson pointer pointing to employee object
ptrPersonObj = &EmployeeObj;
//displaying employee info
ptrPersonObj ->Display();
//writing empolyee info in the employeedata.txt file
ptrPersonObj ->WriteToFile("employeedata.txt");
在C中,繼承可以通過在派生類對象中維護一個基類對象的引用來完成。在基類實例的幫助下,women可以訪問基類的數據成員和函數。然而,為了實現多態,街壘對象應該能夠訪問派生類對象的數據。為了實現這個,基類應該有訪問派生類的數據成員的權限。
為了實現虛函數,派生類的函數簽名應該和基類的函數指針類似。即派生類函數將以基類對象的一個實例為參數。我們在基類中維護一個派生類的引用。在函數實現上,我們可以從派生類的引用訪問實際派生類的數據。
C中的繼承-Person和Employee結構體:

如圖所示,我們在基類結構體中聲明了一個指針保存派生類對像,並在派生類結構體中聲明一個指針保存基類對象。
在基類對象中,函數指針指向自己的虛函數。在派生類對象的構造函數中,我們需要使基類的接口指向派生類的成員函數。這使我們可以通過基類對象(多態)靈活的調用派生類函數。更多細節,請檢查Person和Employee對象的構造函數。
當我們討論C++中的多態時,有一個對象銷毀的問題。為了正確的清楚對象,它使用虛析構函數。在C中,這可以通過使基類的刪除函數指針指向派生類的析構函數。派生類的析構函數清楚派生類的數據和基類的數據和對象。注意:檢查例子的源碼中,實現須構造函數和虛函數的實現細節。
//Person.h
typedef struct _Person Person;
//pointers to function
typedef void (*fptrDisplayInfo)(Person*);
typedef void (*fptrWriteToFile)(Person*, const char*);
typedef void (*fptrDelete)(Person*) ;
typedef struct _person
{
void* pDerivedObj;
char* pFirstName;
char* pLastName;
fptrDisplayInfo Display;
fptrWriteToFile WriteToFile;
fptrDelete Delete;
}person;
Person* new_Person(const char* const pFristName,
const char* const pLastName); //constructor
void delete_Person(Person* const pPersonObj); //destructor
void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, const char* const pFileName);
//Person.c
//construction of Person object
Person* new_Person(const char* const pFirstName, const char* const pLastName)
{
Person* pObj = NULL;
//allocating memory
pObj = (Person*)malloc(sizeof(Person));
if (pObj == NULL)
{
return NULL;
}
//pointing to itself as we are creating base class object
pObj->pDerivedObj = pObj;
pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1));
if (pObj->pFirstName == NULL)
{
return NULL;
}
strcpy(pObj->pFirstName, pFirstName);
pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1));
if (pObj->pLastName == NULL)
{
return NULL;
}
strcpy(pObj->pLastName, pLastName);
//Initializing interface for access to functions
//destructor pointing to destrutor of itself
pObj->Delete = delete_Person;
pObj->Display = Person_DisplayInfo;
pObj->WriteToFile = Person_WriteToFile;
return pObj;
}

//Employee.h
#include "Person.h"
typedef struct _Employee Employee;
//Note: interface for this class is in the base class
//object since all functions are virtual.
//If there is any additional functions in employee add
//interface for those functions in this structure
typedef struct _Employee
{
Person* pBaseObj;
char* pDepartment;
char* pCompany;
int nSalary;
//If there is any employee specific functions; add interface here.
}Employee;
Person* new_Employee(const char* const pFirstName, const char* const pLastName,
const char* const pDepartment, const char* const pCompany,
int nSalary); //constructor
void delete_Employee(Person* const pPersonObj); //destructor
void Employee_DisplayInfo(Person* const pPersonObj);
void Employee_WriteToFile(Person* const pPersonObj, const char* const pFileName);
//Employee.c
Person* new_Employee(const char* const pFirstName, const char* const pLastName,
const char* const pDepartment,
const char* const pCompany, int nSalary)
{
Employee* pEmpObj;
//calling base class construtor
Person* pObj = new_Person(pFirstName, pLastName);
//allocating memory
pEmpObj = malloc(sizeof(Employee));
if (pEmpObj == NULL)
{
pObj->Delete(pObj);
return NULL;
}
pObj->pDerivedObj = pEmpObj; //pointing to derived object
//initialising derived class members
pEmpObj->pDepartment = malloc(sizeof(char)*(strlen(pDepartment)+1));
if(pEmpObj->pDepartment == NULL)
{
return NULL;
}
strcpy(pEmpObj->pDepartment, pDepartment);
pEmpObj->pCompany = malloc(sizeof(char)*(strlen(pCompany)+1));
if(pEmpObj->pCompany== NULL)
{
return NULL;
}
strcpy(pEmpObj->pCompany, pCompany);
pEmpObj->nSalary = nSalary;
//Changing base class interface to access derived class functions
//virtual destructor
//person destructor pointing to destrutor of employee
pObj->Delete = delete_Employee;
pObj->Display = Employee_DisplayInfo;
pObj->WriteToFile = Employee_WriteToFile;
return pObj;
}

注意:從基類函數到派生類函數改變了接口(VTable)中指針位置。現在我們可以從基類(多態)訪問派生類函數。我們來看如何使用多態。
Person* PersonObj = new_Person("Anjali", "Jaiswal");
Person* EmployeeObj = new_Employee("Gauri", "Jaiswal","HR", "TCS", 40000);
//accessing person object
//displaying person info
PersonObj->Display(PersonObj);
//writing person info in the persondata.txt file
PersonObj->WriteToFile(PersonObj,"persondata.txt");
//calling destructor
PersonObj->Delete(PersonObj);
//accessing to employee object
//displaying employee info
EmployeeObj->Display(EmployeeObj);
//writing empolyee info in the employeedata.txt file
EmployeeObj->WriteToFile(EmployeeObj, "employeedata.txt");
//calling destrutor
EmployeeObj->Delete(EmployeeObj);
使用上面描述的簡單的額外代碼能是過程式C語言有多態和繼承的特性。我們簡單的使用函數指針創建一個VTable和在基類和派生類對象中交叉維護引用。用這些簡單的步驟,我們在C中可以實現繼承和多態。