文章目录
前言
前几天部门内训,部长给我们讲了如何在c语言实现面向对象,之前有过三脚猫的java,我本以为不难理解,结果对c语言理解太浅导致什么也没听懂,遂自行搜索钻研,好像学到一些,作此博客,以供回忆学习
面向对象编程思想
面向对象思想有三大核心要素
- 封装
- 继承(extends)
- 多态
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_getAge
和 dog_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);
}
后记
我知道我可能说了依托答辩,但我尽力了