引言
多态指同一个接口的多种实现方式,是面向对象的核心。在执行过程中,可执行程序根据执行对象的不同,展现不同的行为。多态的存在,使得同样的代码,可以展现出不同的特性,这也是多态最引人入胜的地方。
然而,如何才可以实现多态呢?一般实现多态需要满足三个必要条件:
- 必须要有继承
- 必须要有重写
- 必须要父类引用/指针指向子类对象
本文将从多态的三个必要条件入手,介绍一种类C++的多态实现范式。同《C语言:对象继承之单继承》所述,本文依然借助struct和指针实现对象继承机制;通过函数指针重新赋值实现函数的重写;通过父子对象指针偏移实现父子对象指针的切换。
多态:单继承
在阐述C语言单继承多态范式只前,我们需先熟悉C++语言单继承多态的实现模式:
- 声明一个基类Base,定义基类的某个函数为virtual函数;
- 声明一个子类Drived,定义一个函数名以及输入参数相同的函数;
- 定义指向子类Drived的父类Base指针/引用;
- 通过指针/引入调用此函数,子类Drived的函数会被调用,而非父类Base的函数。
熟悉了C++单继承实现模式,我们介绍C语言单继承多态实现范式。此处依然借用Employee和Person。
Person基类
基类Person的接口定义
struct PrivatePersonDesc;
struct PrivatePersonFuncs;
typedef struct Person
{
struct PrivatePersonDesc* personDesc;
struct PrivatePersonFuncs* personFuncs;
char* (*getName)(void * self);
void (*setName)(void * self, char* name);
unsigned int(*getAge)(void* self);
void (*setAge)(void* self, unsigned int age);
} Person;
Person* newPerson(char* name, unsigned int age);
Person* placementNewPerson(Person* self, char* name, unsigned int age);
void placementDeletePerson(Person* self);
void deletePerson(Person* self);
基类Person的具体实现
#define MAX_NAME_LENGTH 255
typedef struct PrivatePersonDesc
{
char name[MAX_NAME_LENGTH];
unsigned int age;
}PrivatePersonDesc;
typedef struct PrivatePersonFuncs
{
void (*init)(Person* self, char* name, unsigned int age);
void (*deinit)(Person* self);
}PrivatePersonFuncs;
static void initPerson(Person* self, char* name, unsigned int age)
{
self->personDesc = operatorNew(sizeof(PrivatePersonDesc));
assert(NULL != self->personDesc);
strcpy_s(self->personDesc->name, MAX_NAME_LENGTH, name);
self->personDesc->age = age;
}
static void deinitPerson(Person* self)
{
strcpy_s(self->personDesc->name, MAX_NAME_LENGTH, "");
self->personDesc->age = 0;
operatorDelete(self->personDesc);
self->personDesc = NULL;
}
static Person* createPerson(Person* self, char* name, unsigned int age)
{
PrivatePersonFuncs* personFuncs = (PrivatePersonFuncs*)operatorNew(sizeof(PrivatePersonFuncs));
assert(NULL != personFuncs);
personFuncs->init = initPerson;
personFuncs->deinit = deinitPerson;
self->personFuncs = personFuncs;
personFuncs->init(self, name, age);
return self;
}
static void destroyPerson(Person* self)
{
PrivatePersonFuncs* personFuncs = self->personFuncs;
assert(NULL != personFuncs);
personFuncs->deinit(self);
operatorDelete(personFuncs);
}
static char* getName(void * self)
{
Person *this = self;
printf_s("---Person->getName---\n");
return this->personDesc->name;
}
static void setName(void * self, char* name)
{
Person *this = self;
strcpy_s(this->personDesc->name, MAX_NAME_LENGTH, name);
printf_s("---Person->setName---\n");
return ;
}
static unsigned int getAge(void * self)
{
Person *this = self;
printf_s("---Person->getAge---\n");
return this->personDesc->age;
}
static void setAge(void * self, unsigned int age)
{
Person *this = self;
this->personDesc->age = age;
printf_s("---Person->setAge---\n");
return ;
}
Person* newPerson(char* name, unsigned int age)
{
Person* person = (Person*)operatorNew(sizeof(Person));
assert(NULL != person);
person = placementNewPerson(person, name, age);
return person;
}
Person* placementNewPerson(Person* self, char* name, unsigned int age)
{
assert(NULL != self);
self->getAge = getAge;
self->getName = getName;
self->setAge = setAge;
self->setName = setName;
self = createPerson(self, name, age);
return self;
}
void placementDeletePerson(Person* self)
{
destroyPerson(self);
self->getAge = NULL;
self->getName = NULL;
self->setAge = NULL;
self->setName = NULL;
}
void deletePerson(Person* self)
{
placementDeletePerson(self);
operatorDelete(self);
self = NULL;
}
Employee派生类
标准C++通过虚函数表实现多态。在对象构造时,对象的起始位置会放置一个VPTR(virtual table pointer),VPTR指向虚函数表VTBL(virtual table)的函数指针数组。一个类只有一个VTBL,每个对象都有一个VPTR, 如果一个函数是虚函数,子类对象会用自己的定义的虚函数指针更新此虚函数表;虚函数在调用时,首先会从VTBL中找到当前虚函数对应的真正函数体,然后执行此函数,这一个过程被称之为动态绑定。通过动态绑定技术,一个指向派生类的基类指针就可以获取到子类实现的虚函数体,从而执行派生类的虚函数而非基类的虚函数。这样就完成了一次多态调用。
标准C++虚函数表仅仅是动态绑定理念的一种实现而已,除此之外还有很多的实现范式。本文将介绍一种参考Javascript的原型链模式的多态实现范式。具体原理如下:
- 派生类没有定义自己的派生函数实现,则派生类引用/指针调用,将会执行基类对应的函数实现;
- 派生类定义了自己的派生函数实现,则派生类引用/指针调用,将会执行派生类对应的函数实现。
我们称基类的函数为原型函数,我们称子类中继承的父类原型函为原型函数重写。原型函数重写的调用对应cpp中的虚函数调用。具体实现请大家参考Employee声明和定义。
Employee声明
struct PrivateEmployeeDesc;
struct PrivateEmployeeFuncs;
typedef struct Employee
{
Person person;
struct PrivateEmployeeDesc* employeeDesc;
struct PrivateEmployeeFuncs* employeeFuncs;
void (*setDepartment)(void * self, char* department);
char* (*getDepartment)(void * self);
DECLEAR_PROTO_FUNCTION_0(char*, getName);
DECLEAR_PROTO_FUNCTION_1(void, setName, char*, name);
DECLEAR_PROTO_FUNCTION_0(unsigned int, getAge);
DECLEAR_PROTO_FUNCTION_1(void, setAge, unsigned int, age);
}Employee;
DECLEAR_PROTO_FUNCTION系列宏主要完成原型继承的映射关系,其定义如下:
#define DECLEAR_PROTO_FUNCTION_0(ReturnType, funcName) \
ReturnType (*##funcName)(void * self)
#define DECLEAR_PROTO_FUNCTION_1(ReturnType, funcName, arg0, arg1) \
ReturnType (*##funcName)(void * self, DARG2(arg0, arg1))
#define DECLEAR_PROTO_FUNCTION_2(ReturnType, funcName, arg0, arg1, arg2, arg3) \
ReturnType (*##funcName)(void* self,DARG4(arg0, arg1, arg2, arg3))
Employee定义
Employee定义重点为大家介绍原型函数的定义,原型函数重写的定义,还有原型函数重写的动态绑定。
#define MAX_DEPARTMENT_LENGTH 255
typedef struct PrivateEmployeeDesc
{
char department[MAX_DEPARTMENT_LENGTH];
}PrivateEmployeeDesc;
typedef struct PrivateEmployeeFuncs
{
void(*init)(Employee* self, char* department);
void(*deinit)(Employee* self);
}PrivateEmployeeFuncs;
BEGIN_TYPE_MAP(Employee)
DECLEAR_TYPE_MAP(Employee, Person, person)
DECLEAR_TYPE_MAP(Employee, Role, role)
END_TYPE_MAP()
DEFINE_PROTO_FUNCTION_1(Employee, Person, void, setName, char*, name)
DEFINE_PROTO_FUNCTION_0(Employee, Person, unsigned int, getAge)
DEFINE_PROTO_FUNCTION_1(Employee, Person, void, setAge, unsigned int, age)
static char* getName(void * self)
{
Employee *this = self;
printf_s("---Employee->getName---\n");
return this->person.getName(&this->person);
}
static char* getDepartment(void * self)
{
Employee* this = self;
return this->employeeDesc->department;
}
static void setDepartment(void * self, char* department)
{
Employee* this = self;
strcpy_s(this->employeeDesc->department, MAX_DEPARTMENT_LENGTH, department);
}
static void initEmployee(Employee* self, char* department)
{
self->employeeDesc = operatorNew(sizeof(PrivateEmployeeDesc));
assert(NULL != self->employeeDesc);
strcpy_s(self->employeeDesc->department, MAX_DEPARTMENT_LENGTH, department);
}
static void deinitEmployee(Employee* self)
{
strcpy_s(self->employeeDesc->department, MAX_DEPARTMENT_LENGTH, "");
operatorDelete(self->employeeDesc);
self->employeeDesc = NULL;
}
static Employee* createEmployee(Employee* self, char* department)
{
PrivateEmployeeFuncs* employeeFuncs = (PrivateEmployeeFuncs*)operatorNew(sizeof(PrivateEmployeeFuncs));
assert(NULL != employeeFuncs);
employeeFuncs->init = initEmployee;
employeeFuncs->deinit = deinitEmployee;
self->employeeFuncs = employeeFuncs;
employeeFuncs->init(self, department);
self->getAge = getAge;
self->setAge = setAge;
self->getName = getName;
self->setName = setName;
self->setRoleName = setRoleName;
self->getRoleName = getRoleName;
self->getDepartment = getDepartment;
self->setDepartment = setDepartment;
return self;
}
static void destroyEmployee(Employee* self)
{
PrivateEmployeeFuncs* employeeFuncs = self->employeeFuncs;
assert(NULL != employeeFuncs);
employeeFuncs->deinit(self);
operatorDelete(employeeFuncs);
}
Employee* newEmployee(char* name, unsigned int age, char* department, char* roleName)
{
Employee* employee = (Employee*)operatorNew(sizeof(Employee));
assert(NULL != employee);
employee = placementNewEmployee(employee, name, age, department, roleName);
return employee;
}
Employee* placementNewEmployee(Employee* self, char* name, unsigned int age, char* department, char* roleName)
{
placementNewPerson(&self->person, name, age);
placementNewRole(&self->role, roleName);
self = createEmployee(self, department);
return self;
}
void placementDeleteEmployee(Employee* self)
{
destroyEmployee(self);
placementDeleteRole(&self->role);
placementDeletePerson(&self->person);
}
void deleteEmployee(Employee* self)
{
placementDeleteEmployee(self);
operatorDelete(self);
self = NULL;
}
原型函数及绑定
DEFINE_PROTO_FUNCTION系列函数,实现原型继承原型函数调用转发,具体定义如下:
#define DEFINE_PROTO_FUNCTION_0(DerivedType, BaseType, ReturnType, funcName) \
static ReturnType funcName(void * self) \
{ \
BaseType* baseSelf = TO_BASE(BaseType, DerivedType, self); \
return baseSelf->##funcName(baseSelf); \
}
#define DEFINE_PROTO_FUNCTION_1(DerivedType, BaseType, ReturnType, funcName, arg0, arg1) \
static ReturnType funcName(void* self,DARG2(arg0, arg1)) \
{ \
BaseType* baseSelf = TO_BASE(BaseType, DerivedType, self); \
return baseSelf->##funcName(baseSelf, ARG2(arg0, arg1)); \
}
#define DEFINE_PROTO_FUNCTION_2(DerivedType, BaseType, ReturnType, funcName, arg0, arg1, arg2, arg3) \
static ReturnType funcName(void* self,DARG4(arg0, arg1, arg2, arg3)) \
{ \
BaseType* baseSelf = TO_BASE(BaseType, DerivedType, self); \
return baseSelf->##funcName(baseSelf, ARG2(arg0, arg1, arg2, arg3)); \
}
DEFINE_PROTO_FUNCTION系列函数定义了原型函数调用转发,那如何实现转发函数与调用对象的绑定呢?
其实原型函数的调用转发本质上就是一个函数定义,我们只需要把此转发函数在对象构造时与当前对象关联,就可实现转发函数与调用对象的绑定。createEmployee构造函数中的下述代码块就是负责转发函数与调用对象的绑定。
self->getAge = getAge;
self->setAge = setAge;
self->setName = setName;
原型函数重写
原型函数重写,实现原型函数的实现的重新定义。基于此可实现函数的动态多态功能。如Employee中的getName即是基类原型函数的重写。
static char* getName(void * self)
{
Employee *this = self;
printf_s("---Employee->getName---\n");
return this->person.getName(&this->person);
}
原型函数重写的绑定
原型函数重写虽然实现了原型函数的重新定义,但是重新定义的原型函数并未与调用对象绑定,因此新定义的原型函数重写是不会被调用的。那如何实现原型函数重写与当前对象的绑定呢?
我们可以通过在对象构造时实现原型函数重写与调用对象的绑定,具体请参考代码实现:
static Employee* createEmployee(Employee* self, char* department)
{
PrivateEmployeeFuncs* employeeFuncs = (PrivateEmployeeFuncs*)operatorNew(sizeof(PrivateEmployeeFuncs));
assert(NULL != employeeFuncs);
employeeFuncs->init = initEmployee;
employeeFuncs->deinit = deinitEmployee;
self->employeeFuncs = employeeFuncs;
employeeFuncs->init(self, department);
self->getAge = getAge;
self->setAge = setAge;
self->getName = getName;
self->setName = setName;
self->setRoleName = setRoleName;
self->getRoleName = getRoleName;
self->getDepartment = getDepartment;
self->setDepartment = setDepartment;
return self;
}
此实现通过重新指定当前对象的getName指针实现原型重写与当前对象的绑定,即self->getName = getName。
应用举例
应用例子:
int main()
{
printf("Create Employee(\"employee\", 35, \"Manager\", \"CEO\")\r\n");
Employee* employee = newEmployee("employee", 35, "Manager", "CEO");
printf("Employee name = %s\r\n", employee->getName(employee));
printf("Employee age = %d\r\n", employee->getAge(employee));
printf("Employee department = %s\r\n\r\n", employee->getDepartment(employee));
printf("Create Employee(\"employee\", 35, \"Manager\", \"CEO\")\r\n");
Person* person = TO_BASE(Person, Employee, employee);
printf("Employee name = %s\r\n", person->getName(person));
printf("Employee age = %d\r\n", person->getAge(person));
deleteEmployee(employee);
printf("stEmployee(\"stEmployee\", 30, \"Manager\", \"CFO\")\r\n");
Employee stEmployee;
placementNewEmployee(&stEmployee, "stEmployee", 30, "Manager", "CFO");
printf("Employee name = %s\r\n", stEmployee.getName(&stEmployee));
printf("Employee age = %d\r\n", stEmployee.getAge(&stEmployee));
printf("Employee department = %s\r\n", stEmployee.getDepartment(&stEmployee));
Person* stPerson = TO_BASE(Person, Employee, &stEmployee);
printf("Employee name = %s\r\n", stPerson->getName(stPerson));
printf("Employee age = %d\r\n", stPerson->getAge(stPerson));
placementDeleteEmployee(&stEmployee);
}
运行结果:
Create Employee("employee", 35, "Manager", "CEO")
---Employee->getName---
---Person->getName---
Employee name = employee
---Person->getAge---
Employee age = 35
Employee department = Manager
Create Employee("employee", 35, "Manager", "CEO")
---Person->getName---
Employee name = employee
---Person->getAge---
Employee age = 35
stEmployee("stEmployee", 30, "Manager", "CFO")
---Employee->getName---
---Person->getName---
Employee name = stEmployee
---Person->getAge---
Employee age = 30
Employee department = Manager
---Person->getName---
Employee name = stEmployee
---Person->getAge---
Employee age = 30
源码下载
C语言:多态(单继承实现)源码下载地址。
多态:多继承
在标准C++中,多继承多态和单继承多态实现原理基本是一致的,唯一的区别就是多继承的菱形继承和virtual继承问题,但是本文引入的C语言多态范式,不存在标准C++的菱形继承和virtual继承问题。因为不论是原型函数或原型函数重写,最终都会在子类中以函数声明和定义的方式展现,如果两个基类有相同的函数签名,会导致编译错误。所以关于多继承此处就不在赘述了,具体实现可以参考我写的多继承例子。
源码下载
C语言:多态(多继承实现)源码下载地址。
总结
本文借鉴Javascript原型链继承实现原理,从多态的三个必要条件入手,介绍一种类C++的多态实现范式。此范式可同时支持多继承和单继承,且不存在标准C++中存在的菱形继承问题。与《C语言:对象封装》,《C语言:对象继承之单继承》,《C语言:对象继承之多继承》相互呼应,共同阐述了C语言面向对象的封装,继承和多态三大特性。
|