C语言:多态

C语言:多态,第1张

引言

多态指同一个接口的多种实现方式,是面向对象的核心。在执行过程中,可执行程序根据执行对象的不同,展现不同的行为。多态的存在,使得同样的代码,可以展现出不同的特性,这也是多态最引人入胜的地方。

然而,如何才可以实现多态呢?一般实现多态需要满足三个必要条件:

  • 必须要有继承
  • 必须要有重写
  • 必须要父类引用/指针指向子类对象

本文将从多态的三个必要条件入手,介绍一种类C++的多态实现范式。同《C语言:对象继承之单继承》所述,本文依然借助struct和指针实现对象继承机制;通过函数指针重新赋值实现函数的重写;通过父子对象指针偏移实现父子对象指针的切换。

多态:单继承

在阐述C语言单继承多态范式只前,我们需先熟悉C++语言单继承多态的实现模式:

  1. 声明一个基类Base,定义基类的某个函数为virtual函数;
  2. 声明一个子类Drived,定义一个函数名以及输入参数相同的函数;
  3. 定义指向子类Drived的父类Base指针/引用;
  4. 通过指针/引入调用此函数,子类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的原型链模式的多态实现范式。具体原理如下:

  1. 派生类没有定义自己的派生函数实现,则派生类引用/指针调用,将会执行基类对应的函数实现;
  2. 派生类定义了自己的派生函数实现,则派生类引用/指针调用,将会执行派生类对应的函数实现。

我们称基类的函数为原型函数,我们称子类中继承的父类原型函为原型函数重写。原型函数重写的调用对应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语言面向对象的封装,继承和多态三大特性。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/727786.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存