如何用C语言实现OOP
扫描二维码
随时随地手机看文章
struct
)和函数指针,使用结构体和函数指针便可实现面向对象的三大特性。C语言实现封装
首先我们先简单了解一下什么是封装,简单的说封装就是类将属性和属性操作封装在一个不可分割的独立实体,只提供对外访问属性的操作方法。用户无需知道对象的内部实现细节,但能通过对外提供的接口访问内部属性数据。由于 C 没有像 C 一样可以设置类内部数据的访问权限,所以 C 的属性和操作都是公有的,但是我们可以用 C 的函数指针模仿 C 实现简单的封装。后续的多态实现也用到 C 的函数指针。我们知道 C 所有的非静态成员函数会有一个 this 指针,通过 this 指针可以访问所有的成员变量和成员函数。而 C 可以通过传入成员变量所在的结构体指针,达到 C this 指针的效果。现在我们构建一个简单的 Bird 类,Bird 有名称(Name),颜色(Color),重量(Weight),栖居地(Addr)属性和对应的操作方法。enum{
INVALID_COLOR = 0,
RED = 1,
GREEN = 2,
};
struct Bird{
char *Name;
char *Addr;
int Color;
int Weight;
void (*SetName)(struct Bird *Bird, char *Name);
void (*SetAddr)(struct Bird *Bird, char *Addr);
void (*SetColor)(struct Bird *Bird, const int Color);
void (*SetWeight)(struct Bird *Bird, const int Weight);
char *(*GetName)(struct Bird *Bird);
int (*GetColor)(struct Bird *Bird);
};
代码中 SetName, SetAddr, SetColor, SetWeight 函数指针相当于 C 类的成员函数,是 Bird 类内部数据与外部交互的接口。在 C 中 this 指针是在编译的时候由编译器自己加上去的,所以每个接口都有一个 struct Bird* 类型形参,该指针的作用相当于 C 的 this 指针,通过该指针可以访问类内部的所有成员变量和成员函数。接下来就需要实现具体的函数,再在执行构造函数时手动将函数指针指向最终的实现函数。具体成员函数实现源码如下:void SetBirdName(struct Bird *Bird, const char * const Name)
{
if(Bird == NULL){
return;
}
Bird->Name = Name;
}
void SetBirdAddr(struct Bird *Bird, const char * const Addr)
{
if(Bird == NULL){
return;
}
Bird->Addr = Addr;
}
void SetBirdColor(struct Bird *Bird, const int Color)
{
if(Bird == NULL){
return;
}
Bird->Color = Color;
}
void SetBirdWeight(struct Bird *Bird, const int Weight)
{
if(Bird == NULL){
return;
}
Bird->Weight = Weight;
}
char *GetName(struct Bird *Bird)
{
if(Bird == NULL){
return NULL;
}
return Bird->Name;
}
int GetColor(struct Bird *Bird)
{
if(Bird == NULL){
return INVALID_COLOR;
}
return Bird->Color;
}
那么 C 的构造函数和析构函数如何使用 C 来实现呢?构造函数在创建一个对象实例时自动调用,析构函数则在销毁对象实例时自动调用,实际上 C 的构造函数和析构函数在编译期间由编译器插入到源码中。但是编译 C 源码时,编译器没有这种操作,需要我们手动去调用构造函数和析构函数。而且在调用 C 的构造函数时,需要我们手动将函数指针指向最终的实现函数。在调用 C 的析构函数时,需要我们手动的释放资源。构造函数源码如下:void BirdInit(struct Bird *Bird)
{
if(Bird == NULL){
return;
}
Bird->SetAddr = SetBirdAddr;
Bird->SetColor = SetBirdColor;
Bird->SetName = SetBirdName;
Bird->SetWeight = SetBirdWeight;
Bird->GetColor = GetColor;
Bird->GetName = GetName;
Bird->SetAddr(Bird, "Guangzhou");
Bird->SetColor(Bird, RED);
Bird->SetWeight(Bird, 10);
Bird->SetName(Bird, "Xiaoming");
}
析构函数源码如下:void BirdDeinit(struct Bird *Bird)
{
if(Bird == NULL){
return;
}
memset(Bird, 0, sizeof(struct Bird));
}
至此,C 如何实现面向对象的封装特性已讲完,下面看看我们实际运用的效果。int main(int argc, char *argv[])
{
struct Bird *Bird = (struct Bird *)malloc(sizeof(struct Bird));
BirdInit(Bird); //调用构造函数
Bird->SetName(Bird, "Lihua"); //更改Bird的名称
Bird->SetColor(Bird, GREEN); //更改Bird的颜色
printf("Bird name: %s, color: %d\n", Bird->GetName(Bird), Bird->GetColor(Bird));
BirdDeinit(Bird); //调用析构函数
free(Bird);
Bird = NULL;
return 0;
}
在 mac 上编译执行结果如下:C语言实现继承
我们继续简单了解一下什么是继承,继承就是使用已存在的类的定义基础建立新类的技术。新类可以增加新的数据和方法,但不能选择性的继承父类。而且继承是“is a”的关系,比如老鹰是鸟,但是你不能说鸟就是老鹰,因为还有其他鸟类动物也是鸟。因为 C 语言本身的限制,只能用 C 实现 C 的公有继承(除非使用 C 开发新的计算机语言)。在 C 使用公有继承(没有虚函数),编译器会在编译期间将父类的成员变量插入到子类中,通常是按照顺序插入(具体视编译器决定)。说到这里,我们很容易就能想到如何使用 C 语言实现 C 的公有继承了(不带虚函数),就是在子类中定义一个父类的成员变量,而且父类的成员变量只能放在最开始的位置。依旧使用上面建立的 Bird 类作为父类,我们建立一个新的子类Eagle(老鹰),老鹰可以飞翔也吃肉(其他鸟类不一定会飞和吃肉),所以我们建立的子类如下:struct Eagle
{
struct Bird Bird;
BOOL Fly;
BOOL EateMeat;
void (*CanFly)(struct Bird *Bird, const BOOL Fly);
void (*CanEateMeat)(struct Bird *Bird, const BOOL EateMeat);
BOOL (*IsFly)(struct Bird *Bird);
BOOL (*IsEateMeat)(struct Bird *Bird);
};
extern void EagleInit(struct Eagle *Eagle);
extern void EagleDeinit(struct Eagle *Eagle);
在 C 中 new 一个子类对象,构造函数的调用顺序则是从继承链的最顶端到最底端,依次调用构造函数。而 delete 一个子类对象时,析构函数的调用顺序则是从继承链的最底端到最顶端依次调用。按照这个模式,我们子类(Eagle)的构造函数和析构函数就很容易写了,构造函数和析构函数源码如下所示:void EagleInit(struct Eagle *Eagle)
{
if(Eagle == NULL){
return;
}
BirdInit(