程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 程序設計基石與實踐系列之C中的繼承和多態

程序設計基石與實踐系列之C中的繼承和多態

編輯:關於C

原文出處: CODE PROJECT

 

1、引言

繼承和多態是面向對象語言最強大的功能。有了繼承和多態,我們可以完成代碼重用。在C中有許多技巧可以實現多態。本文的目的就是演示一種簡單和容易的技術,在C中應用繼承和多態。通過創建一個VTable(virtual table)和在基類和派生類對象之間提供正確的訪問,我們能在C中實現繼承和多態。VTable能通過維護一張函數表指針表來實現。為了提供基類和派生類對象之間的訪問,我們可以在基類中維護派生類的引用和在派生類中維護基類的引用。

2、說明

在C中實現繼承和多態之前,我們應該知道類(Class)在C中如何表示。

2.1、類在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中為了訪問類的數據成員,我們需要把調用對象作為函數參數傳遞。上面的例子中,我們把調用對象作為函數的第一個參數,通過這種方法,函數可以訪問對象的數據成員。

 

3、在C中類的表現

Person類的表示——檢查初始化接口指向成員函數:

/

3.1、繼承和多態的簡單例子

繼承-Employee類繼承自Person類:

data-cke-saved-src=https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010313425978.jpg

在上面的例子中,類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可以訪問基類的數據成員和函數。然而,為了實現多態,街壘對象應該能夠訪問派生類對象的數據。為了實現這個,基類應該有訪問派生類的數據成員的權限。

 

為了實現虛函數,派生類的函數簽名應該和基類的函數指針類似。即派生類函數將以基類對象的一個實例為參數。我們在基類中維護一個派生類的引用。在函數實現上,我們可以從派生類的引用訪問實際派生類的數據。

3.2、在C中結構體中的等效表示

C中的繼承-Person和Employee結構體:

data-cke-saved-src=https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010313425932.jpg

如圖所示,我們在基類結構體中聲明了一個指針保存派生類對像,並在派生類結構體中聲明一個指針保存基類對象。

在基類對象中,函數指針指向自己的虛函數。在派生類對象的構造函數中,我們需要使基類的接口指向派生類的成員函數。這使我們可以通過基類對象(多態)靈活的調用派生類函數。更多細節,請檢查Person和Employee對象的構造函數。

當我們討論C++中的多態時,有一個對象銷毀的問題。為了正確的清楚對象,它使用虛析構函數。在C中,這可以通過使基類的刪除函數指針指向派生類的析構函數。派生類的析構函數清楚派生類的數據和基類的數據和對象。注意:檢查例子的源碼中,實現須構造函數和虛函數的實現細節。

創建Person對象

 

//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對象的結構

 

data-cke-saved-src=https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010313425907.jpg

創建Employee對象

 

//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對象的結構

 

data-cke-saved-src=https://www.aspphp.online/bianchen/UploadFiles_4619/201701/2017010313425943.jpg

注意:從基類函數到派生類函數改變了接口(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中可以實現繼承和多態。

 

 

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved