C中的继承和多态

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类:

在上面的例子中,类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结构体:

如图所示,我们在基类结构体中声明了一个指针保存派生类对像,并在派生类结构体中声明一个指针保存基类对象。

在基类对象中,函数指针指向自己的虚函数。在派生类对象的构造函数中,我们需要使基类的接口指向派生类的成员函数。这使我们可以通过基类对象(多态)灵活的调用派生类函数。更多细节,请检查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对象的结构

创建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对象的结构

注意:从基类函数到派生类函数改变了接口(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中可以实现继承和多态。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容