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中可以实现继承和多态。

时间: 2024-09-15 02:36:45

C中的继承和多态的相关文章

Objective-C中的继承和多态

面向对象编程之所以成为主流的编程思想和他的继承和多态是分不开的,只要是面向对象语言都支持继承和多态,当然不同的OOP语言之间都有其特点.OC中和Java类似,不支持多重继承,但OOP语言C++就支持多继承,为什么OC不支持多继承稍后将会提到.  说到继承呢,想到了一本书上是引用<大话西游>里的一句话来描述继承的."人是人他妈生的,妖是妖他妈生的!",想必里面的唐三藏也学过OOP编程,也许他们师徒四人去西天取什么算法导论呢,漫谈OOP编程啦,数据结构啦等这类的书去啦.人和妖都

关于面向对象中,继承、多态问题。

问题描述 最近在学java基础,学完面向对象感觉自己很不理解继承和多态,不知道该怎么用,谁能给我讲下啊,很困惑的说. 解决方案 解决方案二:感觉java编程思想在这一块说的是非常的好.解决方案三:不理解可以先放一放多敲敲代码月往后就会越懂得现在先积累代码量不要一味的抄代码解决方案四:了解---->敲代码----->理解解决方案五:继承就是扩展或派生基类,你可以这样理解:子类内部持有父类的引用,有共同的行为和属性(private,构造器除外),你可以对重写父类的方法来达到多态的目的.而多态就是,

用实例解释Python中的继承和多态的概念_python

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类(Base class.Super class). 比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印: class Animal(object): def run(self): print 'Animal is running...' 当我们需要编写Dog和Cat类时,就可以直接从Animal

Objective-C 继承和多态

学习Java我们知道类有三大特征,封装,继承,多态.而在Objective-C中也有继承的概念,今天就来看看Objective-C中的继承和多态. 首先,我们来看看示例代码: //Animal.h #import <Foundation/Foundation.h> @interface Animal : NSObject { int food_consumption; //食量 int count; //数量 int parturition_days; //生产周期 } -(int)count

C语言模式实现C++继承和多态

C语言模式实现C++继承和多态 描述: C实现一个struct A和struct B各包含一个int成员a和b,要求达到B继承了A的效果,也就是B里面包含一个A.并且能达 到多态的效果,也就是一个A*p指向A调的是A的函数,指向B调用的是B的函数. C++中的继承.多态 继承是面向对象复用的重要手段.通过继承定义一个类,继承是类型之间的关系建模,共享公有的东西,实现各自本质 不同的东西.  继承是一种复用手段,在继承关系里基类的成员类的成员派生类的成员,由此达到复用的目的.  如果你想学习C/C

java-Java中的数据成员继承的多态

问题描述 Java中的数据成员继承的多态 public class PolymorphismTest { public void show() { A a = new B(); B b = new B(); a.show(); b.show(); System.out.println("a.a: " + a.a); System.out.println("b.a: " + b.a); } public class A { public int a = 5; publ

Java中继承、多态、重载和重写介绍_java

什么是多态?它的实现机制是什么呢?重载和重写的区别在那里?这就是这一次我们要回顾的四个十分重要的概念:继承.多态.重载和重写. 继承(inheritance) 简单的说,继承就是在一个现有类型的基础上,通过增加新的方法或者重定义已有方法(下面会讲到,这种方式叫重写)的方式,产生一个新的类型.继承是面向对象的三个基本特征--封装.继承.多态的其中之一,我们在使用JAVA时编写的每一个类都是在继承,因为在JAVA语言中,java.lang.Object类是所有类最根本的基类(或者叫父类.超类),如果

Java中的面向对象封装、抽象、继承、多态特点

 面向对象主要有四大特性:封装.抽象.继承和多态.各自定义: 封装:在面向对象语言中,封装特性是由类来体现的,我们将现实生活中的一类实体定义成类,其中包括属性和行为(在Java中就是方法),就好像人类,可以具有name,sex,age等属性,同时也具有eat(),sleep()等行为,我们在行为中实现一定的功能,也可操作属性,这是面向对象的封装特性: 抽象:抽象就是将一类实体的共同特性抽象出来,封装在一个抽象类中,所以抽象在面向对象语言是由抽象类来体现的.比如鸟就是一个抽象实体,因为抽象实体并不

Cocos2d-JS中JavaScript继承

JavaScript语言本身没有提供类,没有其它语言的类继承机制,它的继承是通过对象的原型实现的,但这不能满足Cocos2d-JS引擎的要求.由于Cocos2d-JS引擎是从Cocos2d-x演变而来的,在Cocos2d-JS的早期版本Cocos2d-HTML中几乎全部的API都是模拟Cocos2d-x API而设计的,Cocos2d-x本身是有C++编写的,其中的很多对象和函数比较复杂,JavaScript语言描述起来有些力不从心了.在开源社区中John Resiq在他的博客(http://e