繼承和多態是面向對象語言最強大的功能。有了繼承和多態,我們可以完成代碼重用。在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中可以實現繼承和多態。