C语言–面向对象编程之封装
C语言–面向对象编程之继承
–
C语言实现面向对象编程的第三篇,在前面两篇里面我们已经讨论了C语言实现面向对象的封装与继承,这篇我们了解,面向对象的最后一个特性,多态,仅仅是用C的方式了解和实现多态,尽可能少用C++的方式解释。
多态指的是能够根据对象的不同类型调用同一个接口,实现不同的功能。在C++中有一个叫做重载的功能,作用类似,比如下面的函数。
看例子:
/*函数重载*/
int add(int a,int b)
{
return (a+b);
}
int add(int a,int b,int c)
{
return (a+b+c);
}
int main(int argc, char** argv)
{
int i,h,k;
add(i,j);
add(i,j,k);
}
下面的函数就是编译器能够通过查看同名函数和传入的不同参数实现调用不同的函数,在C中函数重名会报错,而在C++中则不会(传入参数不一样就行)。这就叫做重载,重载和多态作用是类似的,能够根据不同传入的参数,调用同一个名字的接口实现不同的功能。
那C语言改如何实现多态呢,C编译器又没有函数重载的功能。
–>答案是运用函数指针与函数表,在初始化的时候调用不同的函数创建对象,然后在创建对象的时候,也就是函数体里面的函数指针赋值,指向同一个函数表(函数表里面拥有函数地址),这样就可以访问啦。
图示:
C语言实现多态,理解好指针内存分配和强制类型转换就可以知道,C语言的多态的实现原理。
图示:在下面的demo中,在创建对象是运行的时候进行创建的,而核心就在于函数表绑定的函数并不是实际上调用多态的函数,这样就能够实现不同的函数功能。
调用的多态函数实际上是通过函数指针调用绑定的函数,这样就实现了多态。我们既可以通过函数指针访问对象函数,也可以直接调用多态函数传入不同对象间接调用函数指针访问对象。
需要注意的是:顺序,也就是内存的问题,一定是先绑定,后执行。
了解了基本的实现方式,让我们看看下面的代码叭。
这个简单的demo是和上一篇C语言–面向对象编程之继承文章的一个延续,依旧使用打怪升级的例子,不过这里做个一个小改动。一般宠物都会有一个普通攻击,进化第1级对象的普通攻击动作与进化第2级是不同的,那怎么根据不同进化等级的对象调用同一个普通攻击函数实现不同的动作。
这里做一个限制条件,等进化等级1和进化等级2是一个继承的关系,方便我们理解。当然多态也可以不是继承的关系,可以是不同类型的对象,执行同一个函数,例如吃东西的动作,人的对象和猫的对象调用同一个动作函数,实现是不同的行为。
至于怎么实现不是继承关系的多态,理解了上面的内存分配与函数指针实现起来也是非常简单的。
struct Base_skill_vtabl;
struct Child_skill_vtabl;
/*小米属性*/
typedef struct
{
char name[10];
int Heal_value;
/*函数表指针,C++如果使用的virtual关键词,虚化了称为虚指针,指向函数行为表*/
struct Base_skill_vtabl const *vptr;
}Base_Pets;
/*函数行为表,C++如果使用的virtual关键词,称为虚表*/
struct Base_skill_vtabl
{
int (*illet_skill)(Base_Pets * const Me);
int (*ordinary_skill)(void * const Me);
};
/*大米属性*/
typedef struct
{
/*继承基类属性*/
Base_Pets *base_pet;
/*自己的属性*/
int Magic_value;
struct Child_skill_vtabl const *vptr;
}Child_Pets;
struct Child_skill_vtabl
{
int (*rice_skill)(Child_Pets* const Me);
int (*ordinary_skill)(void * const Me);
};
/*普通函数*/
int Rich_skill(Child_Pets* const Me);
int Illet_skill(Base_Pets * const Me);
int Child_Ordinary_skill(void * const Me);
int Base_Ordinary_skill(void * const Me);
/*多态函数*/
int Ordinary_skill(void * const Me);
//构建基类
Base_Pets * Ctor_base_Pets(char *name)
{
Base_Pets *ptr;
struct Base_skill_vtabl *table;
/*分配空间*/
ptr=(Base_Pets *)malloc(sizeof(Base_Pets));
table=(struct Base_skill_vtabl *)malloc(sizeof(struct Base_skill_vtabl));
//清零
memset(ptr,0,sizeof(struct Base_skill_vtabl));
strcpy(ptr->name,name);
ptr->vptr=table;
table->illet_skill=&Illet_skill;
table->ordinary_skill=&Base_Ordinary_skill;
printf("create Base_Pets class\n");
return ptr;
}
//删除对象/析构对象
void Del_Base_Pets(Base_Pets * const Me)
{
printf("Destroy Base_Pets\n");
if(Me!=NULL)
{
free((void *)Me->vptr);
free(Me);
}
}
//继承基类对象
//构建基类
Child_Pets *Ctor_Child_Pets(char *name,int magic_value)
{
Child_Pets *ptr;
struct Child_skill_vtabl *table;
/*分配空间*/
ptr=(Child_Pets *)malloc(sizeof(Child_Pets));
table=(struct Child_skill_vtabl *)malloc(sizeof(struct Child_skill_vtabl));
//清零
memset(ptr,0,sizeof(struct Child_skill_vtabl));
ptr->base_pet=Ctor_base_Pets(name);
ptr->Magic_value=10;
ptr->vptr=table;
table->rice_skill=&Rich_skill;
table->ordinary_skill=&Child_Ordinary_skill;
printf("create Child_Pets class\n");
return ptr;
}
//删除对象/析构对象
void Del_Child_Pets(Child_Pets * const Me)
{
printf("Destroy Child_Pets\n");
if(Me!=NULL)
{
Del_Base_Pets(Me->base_pet);
free((void *)Me->vptr);
free(Me);
}
}
int Rich_skill(Child_Pets* const Me)
{
printf("use the rich skill\n");
return 1;
}
int Illet_skill(Base_Pets * const Me)
{
printf("use the illet skill\n");
return 1;
}
/*调用接口函数,也就调用函数指针*/
inline int Ordinary_skill(void * const Me)
{
Child_Pets * const _Me=(Child_Pets *)Me;
/*这里仅仅是实现调用的操作*/
return (_Me->vptr->ordinary_skill(Me));
}
/*这里是精髓,通过强制指针转换来达到访问不同对象的目的*/
int Base_Ordinary_skill(void * const Me)
{
/*指针显示转换,就可以传入同一对象的指针,访问不同对象的内容*/
Base_Pets const * const _Me=(Base_Pets * const)Me;
printf("Base_Pets use Ordinary_skill,without magic\r\n");
return 0;
}
/*这里是精髓,通过强制指针转换来达到访问不同对象的目的*/
int Child_Ordinary_skill(void * const Me)
{
/*指针显示转换,就可以访问不同对象的内容*/
Child_Pets const * const _Me=(Child_Pets * const)Me;
printf("Child_Pets use Ordinary_skill,magic val is %d\r\n",_Me->Magic_value);
return 0;
}
int main()
{
//创建对象指针
Child_Pets *test;
//创建对象
test=Ctor_Child_Pets("child",10);
/*普通调用*/
printf("samlpe function \r\n");
test->base_pet->vptr->illet_skill(test->base_pet);
test->vptr->rice_skill(test);
test->vptr->ordinary_skill(test);
printf("polymorphic function \r\n");
/*多态调用*/
Ordinary_skill(test);//传入子类指针
Ordinary_skill(test->base_pet);//传入父类指针
Del_Child_Pets(test);
system("pause");
}
运行结果
create Base_Pets class
create Child_Pets class
samlpe function
use the illet skill
use the rich skill
Child_Pets use Ordinary_skill,magic val is 10
polymorphic function
Child_Pets use Ordinary_skill,magic val is 10
Base_Pets use Ordinary_skill,without magic
Destroy Child_Pets
Destroy Base_Pets
Press any key to continue . . .
实现多态前需要分配内存,并且绑定执行的函数,这样才能通过函数指针访问函数。分配好内存之后,实现多态就是利用好强制类型转换,将指针的类型转换以此来执行不同的对象。通过一个接口函数,执行函数指针指向的函数。
额外介绍我们仅仅通过两个图,大概了解C++的虚指针与虚函数来对比学习C语言实现多态有什么不一样。
如果没学过C++,看图就够了,C++面向对象的内容相对于C语言来说,最重要的就是简化了一些实现,增加了一些内存的释放与一些语法便于实现面向对象。从面向对象的本质出发,两者都是一样的,面向对象的本质是思维。
那我们先看代码叭:依旧用上面的例子
看到下文的表就可以知道,其实和C是类似的,不过,很多事情编译器都给我们做了,比如函数绑定,指针调用这些,不需要我们显示绑定和创建,编译器帮我们搞定了。C++中其实存在一个函数指针(虚指针,类型为虚函数表类似指针),在创建对象的时候会分配一个虚指针(有多态的情况下),C++也是通过函数指针进行多态实现的(证据就是C++创建对象的时候中内存确实是分配了一个指针大小的内存)。这里可以发现其实C++引入了虚指针与虚函数的概念而已,我们用C也能实现面向对象,不过就是比较麻烦。
class illet
{
public:
illet(){}
illet(string name)
{
this->name=name;
this->heal_val=0;
cout<<"create Base_Pets class"<<endl;
}
~illet()
{
cout<<"Destroy Base_Pets"<<endl;
}
virtual int Ordinary_skill()
{
cout<<"Base_Pets use Ordinary_skill,without magic"<<endl;
return 0;
}
protected:
string name;int heal_val;
};
class rich:public illet
{
public:
rich(string name,int m_val)
{
this->name=name;
this->magic_val=m_val;
cout<<"create Child_Pets class"<<endl;
}
~rich()
{
cout<<"Destroy Child_Pets"<<endl;
}
virtual int Ordinary_skill()
{
cout<<"Child_Pets use Ordinary_skill,magic val is "<<this->magic_val<<endl;
return 0;
}
protected:
int magic_val;
};
void test()
{
rich r_t("child",10);
illet i_t("base");
r_t.Ordinary_skill();
i_t.Ordinary_skill();
}
int main()
{
test();
system("pause");
}
执行结果:基本上和C是一样的
create Child_Pets class
create Base_Pets class
Child_Pets use Ordinary_skill,magic val is 10
Base_Pets use Ordinary_skill,without magic
Destroy Base_Pets
Destroy Child_Pets
Destroy Base_Pets
Press any key to continue . . .
大概看看上面的代码,我们下面图示分析C++是怎么执行多态的,其实和C是类似的。
这里大概看看纯虚函数,所谓的纯虚函数,就是创建以一个对象的时候,不创建虚函数表,继承的时候,需要基类重写多态函数,将函数指针(隐式)指向基类虚函数表。可以这样理解,虚函数就是一个声明,实际上并不会分配内存,也就谈不上调用了。
代码如下:
class illet
{
public:
illet(){}
illet(string name)
{
this->name=name;
this->heal_val=0;
cout<<"create Base_Pets class"<<endl;
}
~illet()
{
cout<<"Destroy Base_Pets"<<endl;
}
/*创建纯虚函数*/
virtual int Ordinary_skill()=0;
protected:
string name;int heal_val;
};
class rich:public illet
{
public:
rich(string name,int m_val)
{
this->name=name;
this->magic_val=m_val;
cout<<"create Child_Pets class"<<endl;
}
~rich()
{
cout<<"Destroy Child_Pets"<<endl;
}
virtual int Ordinary_skill()
{
cout<<"Child_Pets use Ordinary_skill,magic val is "<<this->magic_val<<endl;
return 0;
}
protected:
int magic_val;
};
void test()
{
rich r_t("child",10);
// illet i_t("base");不允许调用,编译器报错
r_t.Ordinary_skill();
// i_t.Ordinary_skill();
}
int main()
{
test();
system("pause");
}