C 语言中的继承和多态






4.74/5 (33投票s)
在C语言中实现单层继承和多态。
引言
继承和多态是面向对象编程语言中最强大的特性。通过继承和多态,我们可以实现代码重用。
在C语言中实现多态有很多巧妙的方法。
本文旨在展示一种简单易懂的技术,用于在C语言中应用继承和多态。通过创建VTable(虚函数表)并在基类和派生类对象之间提供适当的访问,我们可以在C语言中实现继承和多态。VTable的概念可以通过维护一个函数指针表来实现。为了在基类对象和派生类对象之间提供访问,我们需要在基类中维护派生类对象的引用,并在派生类中维护基类对象的引用。
描述
在继续介绍C语言中继承和多态的实现之前,我们应该了解C语言中的类表示。
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()
函数充当构造函数。该函数返回新创建的结构体实例。它初始化函数指针的接口以访问其他成员函数。这里需要注意的一点是,我们只定义了公共访问的函数指针。我们没有在接口中提供对私有函数的访问。让我们来看看我们在C语言中Person
类的new_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语言中访问类的类数据成员,我们需要将调用对象作为参数传递给函数。在上面的例子中,我们将调用对象作为第一个参数传递给函数。这样,函数就可以访问对象的类数据成员。
C语言中类的表示
Person
类表示 - 检查接口的初始化,使其指向成员函数
继承和多态的简单示例
继承 - Employee
类从Person
类派生
在上面的例子中,Employee
类继承了Person
类的属性。由于DisplayInfo()
和WriteToFile()
函数是virtual
(虚函数),我们可以从Person
实例访问Employee
对象的相同函数。为此,我们需要使用Employee
类初始化Person
实例。这在多态的情况下是可能的。在多态的情况下,为了解析函数调用,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语言中,可以通过在派生类对象中维护一个基类对象的引用来实现继承。借助基类实例,我们可以访问基类数据成员和函数。但是,为了实现多态,基类对象应该能够访问派生类对象的数据。为此,基类应该有权访问派生类的数据成员。
为了实现虚函数,派生类函数的签名必须与基类函数指针的签名相似。这意味着派生类函数将以基类实例作为参数。我们在基类中维护派生类的引用。在函数实现过程中,我们可以从派生类的引用访问实际的派生类数据。
C结构体中的等效表示
C语言中的继承 - C语言中的Person
和Employee
结构体
如图所示,我们在基类结构体中声明了一个指向派生类对象的指针,并在派生类结构体中声明了一个指向基类对象的指针。
在基类对象中,函数指针指向其自身的虚函数。在派生类对象的构造过程中,我们需要将基类的接口指向派生类的成员函数。这使得我们能够通过基类对象调用派生类函数(多态)。有关更多详细信息,请查看Person
和Employee
对象的构造。
当我们谈论C++中的多态时,会有一个对象销毁的问题。为了正确清理对象,它使用虚析构函数。在C语言中,可以通过使基类的delete函数指针指向派生类析构函数来实现这一点。派生类的析构函数负责清理派生类数据以及基类数据和对象。注意:有关虚析构函数和虚函数实现的详细信息,请查看示例源代码。
//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语言中实现继承和多态。