c语言中面向对象的实现

前言

前几天部门内训,部长给我们讲了如何在c语言实现面向对象,之前有过三脚猫的java,我本以为不难理解,结果对c语言理解太浅导致什么也没听懂,遂自行搜索钻研,好像学到一些,作此博客,以供回忆学习

面向对象编程思想

面向对象思想有三大核心要素

  1. 封装
  2. 继承(extends)
  3. 多态

C语言中一般使用面向过程编程,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步调用,在函数中对数据结构进行处理(执行算法)。

而面向对象把数据和算法封装在一起,形成一个整体,无论是对它的属性进行操作、还是对它的行为进行调用,都是通过一个对象来执行,这就是面向对象编程思想。

还是有点抽象,事实上我自己也不能清晰的表述,但是写起来总归是有区别的

封装

封装是把一个类的属性封起来,只提供特定的接口调用,c语言有结构体,但显然不能完全实现,但实现大概也好,接下来是代码演示

我们以计算机中喜闻乐见的猫狗例子说明

编写父类Animal

在Animal.h中进行结构体的定义和方法声明

#ifndef _ANIMAL_H_
#define _ANIMAL_H_

// Define The Father Struct

typedef struct
{
    int age;
    int weight;
} Animal;


// Constructor构造器


void animal_Ctor(Animal *this, int age, int weight);

// Getter
int animal_getAge(Animal *this);
int animal_getWeight(Animal *this);
#endif

因为c语言没有this这个语法,所以定义方法时往往要将自身传递为this

在Animal.c中进行方法的定义

#include "Animal.h"
#include <assert.h>


void animal_Ctor(Animal *this, int age, int weight)
{
    this->age = age;
    this->weight = weight;
}

// Getter
int animal_getAge(Animal *this)
{
    return this->age;
}
int animal_getWeight(Animal *this)
{
    return this->weight;
}

// 当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。




至此实现了封装这个简易思想,当然不能保证成员属性只通过给定的接口读写

测试一下

#include <stdio.h>
#include "Animal.c"

int main()
{
    Animal a;
    animal_Ctor(&a,1,3);

    printf("age = %d, weight = %d\n",animal_getAge(&a),animal_getWeight(&a));
}

输出结果 age = 1, weight = 3

继承

编写子类Dog

在Dog.h中进行结构体的定义和方法声明

#ifndef _DOG_H_
#define _DOG_H_

#include "Animal.h"

typedef struct {
    Animal super; // 第一个位置放置父类结构
    int legs;      // 添加子类自己的属性
}Dog;

// 子类构造函数声明
void dog_Ctor(Dog *this, int age, int weight, int legs);

// 子类属性声明
int dog_getAge(Dog *this);
int dog_getWeight(Dog *this);
int dog_getLegs(Dog *this);

#endif

Dog 里额外定义了一个legs属性
注意Dog里要定义一个super,也是为了弥补c语言没有super这一用法,定义super可直接将父类属性沿用

在Dog.c中进行方法的定义

#include "Dog.h"

void dog_Ctor(Dog * this,int age,int weight,int legs)
{
    // 第一个位置放置父类结构
    animal_Ctor(&this->super,age,weight);
   // 添加子类自己的属性
    this->legs = legs;
}

int dog_getAge(Dog *this)
{
    return animal_getAge(&this->super);

}
int dog_getWeight(Dog *this)
{
    return animal_getWeight(&this->super);

}
int dog_getLegs(Dog *this)
{
    return this->legs;

}

dog_getAgedog_getWeight 中使用父类方法

dog_getLegs中直接返回子类属性

测试一下

#include <stdio.h>
#include <stdlib.h>

#include "Animal.c"
#include "Dog.c"

int main()
{
    Dog *d = (Dog*)malloc(sizeof(Dog));
    dog_Ctor(d,1,3,4);
    printf("age = %d, weight = %d, legs = %d \n",dog_getAge(d),dog_getWeight(d),dog_getLegs(d));
}

输出结果 age = 1, weight = 3, legs = 4

多态

多态需要有父类引用指向子类对象

编写父类Animal

在Animal.h中做修改

在C++中,如果一个父类中定义了虚函数,那么编译器就会在这个内存中开辟一块空间放置虚表,这张表里的每一个item都是一个函数指针,然后在父类的内存模型中放一个虚表指针,指向上面这个虚表。

上面这段描述不是十分准确,主要看各家编译器的处理方式,不过大部分C++处理器都是这么干的,我们可以想这么理解。
子类在继承父类之后,在内存中又会开辟一块空间来放置子类自己的虚表,然后让继承而来的虚表指针指向子类自己的虚表。

#ifndef _ANIMAL_H_
#define _ANIMAL_H_

// Define The Father Struct


struct AnimalVTable;//提前声明

typedef struct
{
    //父类虚函数表
    struct AnimalVTable *vptr;
    int age;
    int weight;
} Animal;
//虚函数表结构
struct AnimalVTable
{
    void (*say)(Animal * this);
};

// Constructor



void animal_Ctor(Animal *this, int age, int weight);

// Getter
int animal_getAge(Animal *this);
int animal_getWeight(Animal *this);

// 父类中实现的虚函数

void animal_say(Animal * this);

#endif

在Animal.c 中做修改

#include "Animal.h"
#include <assert.h>

// 当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。
// 加了static将不具有全局可见性,所以在最前面定义而不用Animal.h中声明
static void _animal_Say(Animal *this)
{
    assert(0);
    /*assert(0)作用
本文开始提到的代码中assert(0),assert(0)可能的作用总结如下:
1. 捕捉逻辑错误。可以在程序逻辑必须为真的条件上设置断言。除非发生逻辑错误,否则断言对程序无任何影响。即预防性的错误检查,在认为不可能的执行到的情况下加一句ASSERT(0),如果运行到此,代码逻辑或条件就可能有问题。
2. 程序没写完的标识,放个assert(0)调试运行时执行到此为报错中断,好知道成员函数还没写完。
————————————————
版权声明:本文为CSDN博主「latte_coffee」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xiaodoubao124/article/details/46804319*/
}
void animal_Ctor(Animal *this, int age, int weight)
{
    //指向自己的虚函数表
    static struct AnimalVTable animal_vatble = {_animal_Say};
    this->vptr = &animal_vatble;
    this->age = age;
    this->weight = weight;
}

// Getter
int animal_getAge(Animal *this)
{
    return this->age;
}
int animal_getWeight(Animal *this)
{
    return this->weight;
}




void animal_say(Animal *this)
{
    // 如果this实际指向一个子类Dog对象,那么this->vptr这个虚表指针指向子类自己的虚表,
    // 因此,this->vptr->say将会调用子类虚表中的函数。
    this->vptr->say(this);
}

Dog.h不用动而在Dog.c 中做修改

#include "Dog.h"
static void _Dog_Say(Dog *this)
{
    printf("dog say wang wang \n");
}
void dog_Ctor(Dog * this,int age,int weight,int legs)
{
    animal_Ctor(&this->super,age,weight);

    // 定义子类自己的虚函数表
    static struct AnimalVTable dog_vatble = {_Dog_Say};
    
    // 把从父类中继承得到的虚表指针指向子类自己的虚表
    this->super.vptr = &dog_vatble;

    this->legs = legs;
}

int dog_getAge(Dog *this)
{
    return animal_getAge(&this->super);

}
int dog_getWeight(Dog *this)
{
    return animal_getWeight(&this->super);

}
int dog_getLegs(Dog *this)
{
    return this->legs;

}

测试及说明

#include <stdio.h>
#include <stdlib.h>
#include "Animal.c"
#include "Dog.c"

int main()
{
    Dog d;
    dog_Ctor(&d,1,3,4);
    //将d的地址传给Animal的指针,因为结构在内存的地址时连续的,d的第一个属性是Animal类型的super
    //因此这个指针会读到super结束
    //而刚刚super.vptr已赋值为子类自己的虚函数表
    //回顾一下animal方法体
    /*==============================================================
    void animal_say(Animal *this)
    {
        // 如果this实际指向一个子类Dog对象,那么this->vptr这个虚表指针指向子类自己的虚表,
         // 因此,this->vptr->say将会调用子类虚表中的函数。
         this->vptr->say(this);
    }
    ================================================================*/
    Animal *animal = &d;
    animal_say(animal);

}

后记

我知道我可能说了依托答辩,但我尽力了

  • 20
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值