C语言实现封装、继承、多态
文章目录
一. 封装
C语言中虽然没有类,但有struct和指针。我们可以在一个struct中存入数据和函数指针,以此来模拟类行为。
typedef struct _Parent
{
int a;
int b;
void (*print)(struct _Parent *This);
}Parent;
- 封装性的意义在于,函数和数据是绑在一起的,数据和数据是绑在一起的。这样,我们就可以通过简单的一个结构指针访问到所有的数据,遍历所有的函数。封装性,这是类拥有的属性,当然也是数据结构体拥有的属性。
二.继承
- 如果要完全地用C语言实现继承,可能有点难度。但如果只是简单的做一下,
保证子类中含有父类中的所有成员
。这还是不难的。
typedef struct _Child
{
Parent parent;
int c;
}Child;
- 在设计C语言继承性的时候,我们需要做的就是
把基础数据放在继承的结构的首位置即可
。这样,不管是数据的访问、数据的强转、数据的访问都不会有什么问题。
三. 多态
- 这个特性恐怕是面向对象思想里面最有用的了。
- 要用C语言实现这个特性需要一点点技巧,但也不是不可能的。
#include <stdio.h>
#include <stdlib.h>
//模拟一个类A
typedef struct A
{
void *vptr;//虚函数指针
//下面内容可以删掉,写上的目的:看着像一个类
//成员变量
int a;
int b;
void initA(A *p, int a, int b);
//虚函数
void dong1();
void dong2();
}A;
//实现虚函数
void dong1()
{
printf("基类 dong1\n");
}
void dong2()
{
printf("基类 dong2\n");
}
//模拟虚表结构
typedef struct
{
void(*v1)();
void(*v2)();
}Vtable;
//模拟一个类中的虚表
Vtable A_Vtable = { &dong1, &dong2 };//精华在这:★a类可以调用dong1 dong2 这两个函数★
//模拟A类的构造函数
void initA(A *p, int a, int b)//第一个是this指针,在c++中也会默认传一个this指针
{
p->vptr = &A_Vtable;
p->a = a;
p->b = b;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//模拟派生类 B
typedef struct B
{
A a;//父类的内容
//下面内容可以删掉,写上的目的:看着像一个类
int b;
void dong11();
void dong66();
void initB(B* p, int a, int b);
}B;
//模拟派生类的虚函数
void dong11()//模拟覆盖dong1这个函数
{
printf("派生类 dong11\n");
}
void dong66()
{
printf("派生类 dong66\n");
}
//模拟子类虚表,增加了一个函数dong66
typedef struct
{
Vtable vtable;
void(*p)();
}Vtable2;
//子类的虚表
//注意这里大括号一个不能少
//★b类可以调用dong11、dong2、dong66 这三个函数★
Vtable2 B_vtable = { { &dong11, &dong2 }, &dong66 };
//B类构造函数
//为什么要a参数呢?初始化基类
void initB(B* p,int a, int b)
{
//************重点**************
//创建B类,但是需要初始化A类。继承A类中的虚表指针
//initA((A*)p, a, b);
//我们这里用B类自己的虚表,一个类一个虚表
p->a.vptr = &B_vtable;
p->b = b;
}
///////////////////////////////////////////////////////////////////////
//以下内容全是模拟
//测试一下A类
void test1()
{
A aa;
initA(&aa, 10, 20);
((Vtable*)aa.vptr)->v1();
((Vtable*)aa.vptr)->v2();
}
//测试一下B类
void test2()
{
//创建B类
B *b = (B*)malloc(sizeof(B));
initB(b, 10, 20);
//转成A类
A *a = (A*)b;
//现在表面上是父类,而我们可以调用父类没有而子类有的方法
((Vtable2*)(a->vptr))->p();
printf("\n------见证奇迹的时候到了,实现多态------\n\n");
((Vtable*)(a->vptr))->v1();//子类覆盖了父类对象,调用的就是子类对象
((Vtable*)(a->vptr))->v2();//子类没有覆盖父类对象,调用的就是父类对象
}
//test3用来解惑的。其实我们b类不靠A就能调用父类,子类方法,test2实现的多态就是为了模拟C++
void test3()
{
B *b = (B*)malloc(sizeof(B));
initB(b, 10, 20);
//下面两个地址是一样的,这里得仔细考虑。我也是调试了好久才发现其中的奥秘
printf("%d\n", b->a.vptr);
printf("%d\n", ((A*)b)->vptr);
((Vtable*)(b->a.vptr))->v1();
((Vtable*)(b->a.vptr))->v2();
((Vtable2*)(b->a.vptr))->p();
}
int main()
{
test1();
printf("---------------------------------------------\n\n");
test2();
printf("---------------------------------------------\n\n");
test3();
return 0;
}
/*
**************************上面看着费劲----光看下面就行了
typedef struct
{
void(*v1)();
void(*v2)();
}Vtable;
Vtable A_Vtable = { &dong1, &dong2 };//如果不单独测试A 这句话就是废话
typedef struct
{
//Vtable2有一个Vtable,所以Vtable2可以转化为Vtable使用v1 v2。
Vtable vtable;
//但是我们赋值的时候是Vtable2 所以真真调用的时候,还是从Vtable2中调用
void(*p)();
}Vtable2;
Vtable2 B_vtable = { { &dong11, &dong2 }, &dong66 };
所以,至始至终我们利用的就是Vtable2指向的函数。可以调用v1那是因为Vtable2中有Vtable,而Vtable中有v1
*/