1、引言
繼承和多型是面嚮物件語言最強大的功能。有了繼承和多型,我們可以完成程式碼重用。在C中有許多技巧可以實現多型。本文的目的就是演示一種簡單和容易的技術,在C中應用繼承和多型。通過建立一個VTable(virtual table)和在基類和派生類物件之間提供正確的訪問,我們能在C中實現繼承和多型。VTable能通過維護一張函式表指標表來實現。為了提供基類和派生類物件之間的訪問,我們可以在基類中維護派生類的引用和在派生類中維護基類的引用。
2、說明
在C中實現繼承和多型之前,我們應該知道類(Class)在C中如何表示。
2.1、類在C中的表示
考慮C++中的一個類”Person”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//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中表示上面的類,我們可以使用結構體,並用操作結構體的函式表示成員函式。
1 2 3 4 5 6 7 8 9 10 11 12 |
//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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//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的建構函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//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; } |
建立完物件之後,我們能夠訪問它的資料成員和函式。
1 2 3 4 5 6 7 8 |
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中為了訪問類的資料成員,我們需要把呼叫物件作為函式引數傳遞。上面的例子中,我們把呼叫物件作為函式的第一個引數,通過這種方法,函式可以訪問物件的資料成員。
3、在C中類的表現
Person類的表示——檢查初始化介面指向成員函式:
3.1、繼承和多型的簡單例子
繼承-Employee類繼承自Person類:
在上面的例子中,類Employee繼承類Person的屬性。因為DisplayInfo()和WriteToFile()函式是virtual的,我們能夠從Person的例項訪問Employee物件中的同名函式。為了實現這個,我們建立Person例項的時候也初始化Employee類。多型使這成為可能。 在多型的情況下,去解析函式呼叫,C++使用VTable——即一張函式指標表。
前面我們在結構體中維護的指向函式的指標介面的作用類似於VTable。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//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可以訪問基類的資料成員和函式。然而,為了實現多型,街壘物件應該能夠訪問派生類物件的資料。為了實現這個,基類應該有訪問派生類的資料成員的許可權。
為了實現虛擬函式,派生類的函式簽名應該和基類的函式指標類似。即派生類函式將以基類物件的一個例項為引數。我們在基類中維護一個派生類的引用。在函式實現上,我們可以從派生類的引用訪問實際派生類的資料。
3.2、在C中結構體中的等效表示
C中的繼承-Person和Employee結構體:
如圖所示,我們在基類結構體中宣告瞭一個指標儲存派生類對像,並在派生類結構體中宣告一個指標儲存基類物件。
在基類物件中,函式指標指向自己的虛擬函式。在派生類物件的建構函式中,我們需要使基類的介面指向派生類的成員函式。這使我們可以通過基類物件(多型)靈活的呼叫派生類函式。更多細節,請檢查Person和Employee物件的建構函式。
當我們討論C++中的多型時,有一個物件銷燬的問題。為了正確的清楚物件,它使用虛解構函式。在C中,這可以通過使基類的刪除函式指標指向派生類的解構函式。派生類的解構函式清楚派生類的資料和基類的資料和物件。注意:檢查例子的原始碼中,實現須建構函式和虛擬函式的實現細節。
建立Person物件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
//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; } |
Person物件的結構
建立Employee物件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
//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; } |
Employee物件的結構
注意:從基類函式到派生類函式改變了介面(VTable)中指標位置。現在我們可以從基類(多型)訪問派生類函式。我們來看如何使用多型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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中可以實現繼承和多型。