C语言实现类

c语言实现类

学习嵌入式,C语言是必须学习的一门语言,C语言在设计之初是为了提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言不光提供了许多低级处理的功能,而且保持着良好跨平台的特性,以一个标准规格写出的C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台。
掌握了C语言,其他的语言也会很容易上手,一些面向对象的语言比如:C++、JAVA等都会很轻松的上手。
我们都知道面向对象的三大基本特征:封装、继承和多态,C++语言和编译器都对这些特征有着强有力的支持,但是对于C这样的函数式语言,如何实现面向对象?引用一句话:面向对象从来都是思想,而不是语言! 理解面向对象的编程思想,我们使用C语言这样的较低级的语言也同样可以实现OOP,里面具体用到的有C语言中的宏,结构体,函数指针, 聚合组合等知识。 在C中有许多技巧可以实现多态。
本文的目的是使用C语言实现继承和多态。通过创建一个VTable(virtual table)和在基类和派生类对象之间提供正确的访问,我们能在C中实现继承和多态。VTable能通过维护一张函数表指针表来实现。为了提供基类和派生类对象之间的访问,我们可以在基类中维护派生类的引用和在派生类中维护基类的引用。

在C中实现继承和多态之前,首先我们看看知道类(Class)在C++中如何表示。


1、类在C中的表示

在C++中的创建一个类"Person"。

//Person.h
class Person
{
private:
    char* pFirstName;
    char* pLastName;
    
public:
    Person(constchar* pFirstName, constchar* pLastName);    //constructor
    ~Person();    //destructorvoid displayInfo();
    void writeToFile(constchar* pFileName);

};
用C语言来表示上面的类,我们可以使用结构体,并用操作结构体的函数表示成员函数。
//Person.h
typedef struct _Person
{
    char* pFirstName;
    char* pLastName;
}Person;

void 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* const pFileName);

这里,定义的操作结构体Person的函数没有封装。为了实现封装,即绑定数据、函数、函数指针。我们需要创建一个函数指针表。

构造函数new_Person()将设置函数指针值以指向合适的函数。这个函数指针表将作为对象访问函数的接口。


2. 下面我们重新定义C中实现类Person。

//Person.h
typedef struct _Person Person;

//declaration of pointers to functions
typedef void    (*fptrDisplayInfo)(Person*);
typedef void    (*fptrWriteToFile)( Person*, constchar*);
typedef void    (*fptrDelete)( Person *) ;
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(constchar* const pFirstName, constchar* 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;
}
 
  
其他几个方法的实现:
    
void delete_Person(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
free(pPersonObj);
}
void Person_DisplayInfo(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
printf("pFirstName:%s pLastName:%s\n",pPersonObj->pFirstName,pPersonObj->pLastName);
}
void Person_WriteToFile(Person* const pPersonObj, const char* pFileName)
{
printf("call Person_WriteToFile()\n");
}

创建完对象之后,我们能够访问它的数据成员和函数。

   
main()
{
Person* pPersonObj = new_Person("pengdan", "farsight");
pPersonObj->Display(pPersonObj);
pPersonObj->WriteToFile(pPersonObj, "persondata.txt");
pPersonObj->Delete(pPersonObj);
pPersonObj = NULL;
}

注意:不像C++,在C中我们不能在函数中直接访问数据成员。在C++中,可以隐式通过“this”指针直接访问数据成员。
我们知道C中是没有“this”指针的,通过显示地传递对象给成员函数。在C中为了访问类的数据成员,
我们需要把调用对象作为函数参数传递。上面的例子中,我们把调用对象作为函数的第一个参数,通过这种方法,
函数可以访问对象的数据成员。

3、在C中类的表现

Person类的表示——检查初始化接口指向成员函数:

3.1、继承和多态的简单例子

继承-Employee类继承自Person类:


image

在上面的例子中,类Employee继承类Person的属性。因为DisplayInfo()和WriteToFile()函数是virtual的,

我们能够从Person的实例访问Employee对象中的同名函数。为了实现这个,我们创建Person实例的时候也初始化Employee类。

多态使这成为可能。 在多态的情况下,去解析函数调用,C++使用VTable——即一张函数指针表。

前面我们在结构体中维护的指向函数的指针接口的作用类似于VTable。

//Polymorphism in C++
Person PersonObj("pengdan", "farsight");
Employee EmployeeObj("kouxiaojuan", "liuyan", "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*) ;

typedefstruct _person
{
    void* pDerivedObj;
    char* pFirstName;
    char* pLastName;
    fptrDisplayInfo Display;
    fptrWriteToFile WriteToFile;
    fptrDelete        Delete;
}person;

Person* new_Person(constchar* const pFristName, 
                   constchar* const pLastName);    //constructor
void delete_Person(Person* const pPersonObj);    //destructor
void Person_DisplayInfo(Person* const pPersonObj);
void Person_WriteToFile(Person* const pPersonObj, constchar* const pFileName);
    
//Person.c//construction of Person object
Person* new_Person(constchar* const pFirstName, constchar* 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;
}
其他几个方法的实现:
      
void delete_Person(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
free(pPersonObj);
}
void Person_DisplayInfo(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
printf("pFirstName:%s pLastName:%s\n",pPersonObj->pFirstName,pPersonObj->pLastName);
}
void Person_WriteToFile(Person* const pPersonObj, const char* pFileName)
{
printf("call Person_WriteToFile()\n");
}


Person对象的结构 创建Employee对象

//Employee.h

#include "Person.h"

typedef struct _Employee Employee; 
typedefstruct _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; 
    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;
        
    pObj->Delete = delete_Employee;
    pObj->Display = Employee_DisplayInfo;
    pObj->WriteToFile = Employee_WriteToFile;

    return pObj;
}
Employee对象的结构

image
image

注意:从基类函数到派生类函数改变了接口(VTable)中指针位置。现在我们可以从基类(多态)访问派生类函数。我们来看如何使用多态。

     
main()
{
    Person* PersonObj = new_Person("pengdan", "farsight");
Person* EmployeeObj = new_Employee("kouxiaojuan", "liuyan","HR", "TCS", 40000);

PersonObj->Display(PersonObj); PersonObj->WriteToFile(PersonObj,"persondata.txt"); PersonObj->Delete(PersonObj); EmployeeObj->Display(EmployeeObj); EmployeeObj->WriteToFile(EmployeeObj, "employeedata.txt"); EmployeeObj->Delete(EmployeeObj);
}

结论:

通过以上例子,我们看到使用c语言也可以实现封装、继承、多态,学习好以上知识会有助于我们更好的学习面向对象的语言。



4.Linux内核当中很多机制也有向对象的思想。
4.1下面我们以platform总线来分析
以下是platform总线最重要的两个结构体platform_device 和 platform_driver。


其中 platform_driver中有个很重要的成员  struct device_driver driver;
该结构体是通用的驱动结构体函数,我们可以理解为一个基类,
以下是基于该总线实现的定义的platform_driver结构体变量

static struct platform_driver driver={
.probe = hello_probe,
.driver.name = "fs4412-beep",
.remove = hello_remove,
};
在这里我们可以看到我们对继承过来的driver成员进行了赋值,我们跟踪内核源码 platform_driver_register( )
|
我们可以看到此处,对driver的成员做了进一步赋值,对于driver结构体中几个函数实现方法进行了重新赋值,
因为对于不论属于哪种总线的驱动程序,都需要定义一个driver专用的结构体来维护驱动程序,比如I2C、spi、USB等。
这些总线在实现上
是不同的,但是linux内核要求不论那种总线都要有device_driver类型的成员,有些属性和函数的实现是由device_driver继承来的,类似于类的继承的概念;但是有些成员和函数是要重新编写的,这就类似于面向对象的多态,如在不同的总线下,probe、remove、shutdown几种实现方法不一样。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页