点击上方蓝字关注我们

我们知道面向对象的三大特性分别是:封装、继承、多态。很多语言例如:C++和Java等都是面向对象的编程语言,而我们通常说C是面向过程的语言,那么是否可以用C实现简单的面向对象呢?答案是肯定的!C有一种数据结构叫做结构体(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(&Eagle->Bird);    Eagle->CanFly = CanFly;    Eagle->CanEateMeat = CanEateMeat;    Eagle->IsFly = IsFly;    Eagle->IsEateMeat = IsEateMeat;

    Eagle->CanFly((struct Bird *)Eagle, TRUE);    Eagle->CanEateMeat((struct Bird *)Eagle, TRUE);}

void EagleDeinit(struct Eagle *Eagle){    if(Eagle == NULL){        return;    }    memset(Eagle, 0, sizeof(struct Eagle));    BirdDeinit(&Eagle->Bird);}

在子类的构造函数EagleInit中先调用父类的构造函数BirdInit,在子类的析构函数中先释放子类的资源再调用父类的析构函数BirdDeinit。至此,我们完成了C语言实现C++的公有继承(不带虚函数)。

C语言实现多态

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。老惯例,我们来看一下C++是如何实现运行时多态的。C++的运行时多态是用虚函数实现的。在C++中有虚函数的类存在一个虚函数表指针vptr指向一个虚函数表。而虚函数表则存放着,虚函数对应的实现函数。我们用C语言实现类似于C++的多态性,可以模仿C++用创建虚函数表和在类中定义一个虚函数表指针实现。但是我们一般不用这样实现,因为这种实现方式有几个缺点:

1、添加和删除一个虚函数时,虚函数表大小要随着改变,函数在虚函数表里面存放的位置也要随着改变。

2、会增加类的内存占用空间。

3、多层间接访问虚函数,增加了运行开销和系统复杂度。

通过仔细观察C语言实现继承我们可以知道,父类的成员变量会全部放入到子类内存空间中。那么我们是否可以把虚函数表直接放在类中呢?这个时候函数指针又发挥作用了!我们可以把多个函数指针放在父类中,就可以在之类构造函数中直接将父类里的函数指针重新指向新的实现函数,这就实现了我们想要的多态性!因为鸟类都会下蛋,所以我们定义一个下蛋的函数LayEggs。

Bird类源码如下:

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);

    void (*LayEggs)(struct Bird *Bird);};extern void BirdInit(struct Bird *Bird);extern void BirdDeinit(struct Bird *Bird);

Bird类构造函数源码如下:

static void LayEggs(struct Bird *Bird){    if(Bird == NULL){        return;    }

    printf("bird lay eggs\n");}

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->LayEggs = LayEggs;

    Bird->SetAddr(Bird, "Guangzhou");    Bird->SetColor(Bird, RED);    Bird->SetWeight(Bird, 10);    Bird->SetName(Bird, "Xiaoming");}

Eagle类构造函数源码如下:

static void LayEggs(struct Bird *Bird){    if(Bird == NULL){        return;    }

    printf("Eagle lay eggs\n");}

void EagleInit(struct Eagle *Eagle){    if(Eagle == NULL){        return;    }    BirdInit(&Eagle->Bird);    Eagle->CanFly = CanFly;    Eagle->CanEateMeat = CanEateMeat;    Eagle->IsFly = IsFly;    Eagle->IsEateMeat = IsEateMeat;

    Eagle->Bird.LayEggs = LayEggs;

    Eagle->CanFly((struct Bird *)Eagle, TRUE);    Eagle->CanEateMeat((struct Bird *)Eagle, TRUE);}

在Eagle构造函数中,我们将父类的函数指针指向了新的LayEggs函数,在程序运行期间就会调用新的LayEggs函数。我们修改main函数,观察运行结果。

main函数修改如下:

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));    Bird->LayEggs(Bird);    BirdDeinit(Bird); //调用析构函数    free(Bird);    Bird = NULL;

    Bird = (struct Bird *)malloc(sizeof(struct Eagle));    struct Eagle *Eagle = (struct Eagle *)Bird;    EagleInit((struct Eagle *)Bird);    Bird->SetName(Bird, "Tanmeimei");    Bird->SetAddr(Bird, "Shanghai");    Bird->SetColor(Bird, RED);    printf("Eagle is fly: %d, is eate meat: %d\n", Eagle->IsFly((struct Bird *)Eagle), Eagle->IsEateMeat((struct Bird *)Eagle));    printf("Eagle name is: %s,\n", Bird->GetName(Bird));    Bird->LayEggs(Bird);    EagleDeinit((struct Eagle *)Bird);    free(Bird);    Bird = NULL;

    return 0;}

运行结果如下:

到目前为止,我们已经用C语言实现了封装、继承和多态三大面向对象特性!项目源码:https://gitee.com/C-Cplusplusyiyezhiqiu/wechat-official-account.git

c语言如何实现水平和垂直镜像_如何用C语言实现OOP相关推荐

  1. 摄像头水平视野垂直视野?_如何在“动物穿越:新视野”中的梦中游览某人的岛屿...

    摄像头水平视野垂直视野? The promised second summer update for Animal Crossing: New Horizons has been released, ...

  2. 摄像头水平视野垂直视野?_动物穿越:新视野的梦想更新

    摄像头水平视野垂直视野? So-an update has come to Animal Crossing: New Horizons. This means the arrival of a new ...

  3. 摄像头水平视野垂直视野?_如何在“动物穿越:新视野”中定时旅行

    摄像头水平视野垂直视野? Time travel can be a messy subject for fans familiar with the Animal Crossing series-it ...

  4. 摄像头水平视野垂直视野?_如何在“动物穿越:新视野”中使用Amiibo卡

    摄像头水平视野垂直视野? Animal Crossing: New Horizons offers the chance to get the villager you want by using a ...

  5. python搭建自动化测试平台_如何用python语言搭建自动化测试环境

    原标题:如何用python语言搭建自动化测试环境 技术分享:基于Python语言的Web自动化测试环境搭建 近期发现很多初学者在学习自动化的过程当中,在环境安装环节总是出现问题,所以详细的出一篇环境搭 ...

  6. 语言程序推箱子课设报告_学完C语言,可以去哪些应用领域工作?

    C语言是目前世界上流行.使用非常广泛的高级程序设计语言. 在TIOBE已公布2020年8月的编程语言排行榜.C语言依然保持排行第一! 前20名排行如下: C语言对操作系统和系统使用程序以及需要对硬件进 ...

  7. 如何用c语言读取硬盘串号_如何用C语言实现OOP

    我们知道面向对象的三大特性分别是:封装.继承.多态.很多语言例如:C++和Java等都是面向对象的编程语言,而我们通常说C是面向过程的语言,那么是否可以用C实现简单的面向对象呢?答案是肯定的!C有一种 ...

  8. c语言读取一个图像文件格式,求指导,如何用c语言实现读取*.raw格式图像

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 /* ** 这个程序是读取jpg图像的 ** 后续加上jpg图像打开和存放 */ #include #include #include #include ...

  9. python编程计算1!+2!+...+10!_如何用C语言编程计算 1!+2!+3!+…+10!?

    解决这个问题,首先要明白阶乘. n!=n*(n-1)*(n-2)*--*1:就是自己乘以自己减一,一直乘到一. 循环实现 这个过程必定是一系连续相乘的过程,一直重复着"乘"这个动作 ...

最新文章

  1. Lambda 表达式基础理论与示例
  2. java用opencv实现滤镜_opencv滤镜-二值化实现黑白滤镜
  3. Python 数据分析与展示笔记3 -- Matplotlib 库基础
  4. 写给设计师同学的xcode使用教程: 教你用 Xcode 做原型设计
  5. Linux 内存管理 | 物理内存管理:物理内存、内存碎片、伙伴系统、slab分配器
  6. ANSYS 简支梁的约束
  7. Linux_linux基础命令(增删查,权限,Linux下的重要目录,重要命令(. du, df, top, free, pstack, su, sudo).安装gcc/g++, gdb, vim )
  8. 文件操作(解密加密)
  9. elipse安装php
  10. rabbitmq视频教程,面试官:
  11. php之thinkphp3.2.3 文件访问路径,URL路由配置-与重定向
  12. arduino的串口缓冲区_C#无法从串口Arduino读取完整缓冲区
  13. Python 通过打码平台实现验证码
  14. 运放虚短虚断的成立条件
  15. Facebook广告的基础认知,看一下你是否了解
  16. python与会计学_财务与会计前沿讲座——“大数据集训”开讲
  17. php商品评价,商品评价,评价,商品详情,商品评价api,api,评价api,商品详情
  18. H5实现输入框添加语音功能的方法详解
  19. 关于String的intern的一个题目
  20. CityEngine 三维管道建模教程

热门文章

  1. QQ空间相册展示特效
  2. ZooKeeper管理员指南——部署与管理ZooKeeper
  3. hdu 2544 最短路 (dijkstra)
  4. 【Demo 0116】堆的使用
  5. 查询方式中断方式_【每日“一”题】中断方式
  6. mysql迁移之后读取速度变慢_如何解决数据库迁移之后变慢的问题
  7. 华为云设计语言_《好设计,有方法:我们在搜狐做产品体验设计》 —2.2 设计语言带来的好处...
  8. 虚拟成像技术_AI帝国将崛起,国内幻真虚拟成像技术第一家
  9. 脚本必须位于html的,js 前端第三剑客
  10. java io流大全_Java IO流系统整理