面向对象的三大基本特性就是:封装、继承和多态。我们挨个实现:
封装
封装就是把数据打包到一个类里面,并对不同的数据提供不同的访问权限。在C语言下打包数据还是很容易的。C标准库中的fopen()、fread()等函数的操作对象就是FILE,它的数据内容就是FILE。只不过相比C++而言,我们需要在函数中显示添加被编译器隐式添加的this指针。
//Shape的属性
typedef struct {
int32_t x;
int32_t y;
} Shape;
//Shape的操作函数,接口函数
void Shape_ctor(Shape* const me, int32_t x, int32_t y);
void Shape_dtor(Shape* const me);
void Shape_moveBy(Shape* const me, int32_t x, int32_t y);
int Shape_getX(const Shape* const me);
int Shape_getY(const Shape* const me);
以上是Shape类的声明,放在头文件Shape.h,接下来实现Shape的定义。
当然,我们也可以通过在结构体中加入函数指针来实现成员方法。不过这种方法内存开销较大,每个实例化的对象中都含有函数指针。故我们在这使用全局函数来实现。
#include "shape.h"
// 构造函数
void Shape_ctor(Shape* const me, int32_t x, int32_t y)
{
me->x = x;
me->y = y;
}
//析构函数
void Shape_dtor(Shape* const me)
{
me->x = 0;
me->y = 0;
}
void Shape_moveBy(Shape* const me, int32_t x, int32_t y) {
me->x += x;
me->y += y;
}
// 获取属性值函数
int Shape_getX(const Shape* const me)
{
return me->x;
}
int Shape_getY(const Shape* const me)
{
return me->y;
}
继承
继承就是基于现有的一个类去定义一个新的类,这样有助于重用代码。在C语言里,实现单继承只需要把基类放到继承类的第一个数据成员的位置就可以了。
例如,我们现在需要创建一个Rectangle类,只要继承Shape类已经有的属性和操作,再添加不同于基类的属性和操作到派生类中。
#include "shape.h" // 基类接口
// 矩形的属性
typedef struct {
Shape super; // 继承 Shape
// 自己的属性
uint32_t width;
uint32_t height;
} Rectangle;
// 构造函数
void Rectangle_ctor(Rectangle* const me, int32_t x, int32_t y,
uint32_t width, uint32_t height);
接下来是类的定义:
#include "rect.h"
// 构造函数
void Rectangle_ctor(Rectangle* const me, int32_t x, int32_t y,
uint32_t width, uint32_t height)
{
/*先调用基类的构造函数*/
Shape_ctor(&me->super, x, y);
/*构造派生类特有的属性*/
me->width = width;
me->height = height;
}
因为在派生类中,基类就放在派生类的内的第一个数据成员的位置。故我们可以很安全的将一个指向派生类对象的指针传递一个期望传入基类对象的指针中,这是非常安全的。并且基类的所有属性和方法都可以被派生类继承。
#include "rect.h"
#include <stdio.h>
int main()
{
Rectangle r1, r2;
// 实例化对象
Rectangle_ctor(&r1, 0, 2, 10, 15);
Rectangle_ctor(&r2, -1, 3, 5, 8);
printf("Rect r1(x=%d,y=%d,width=%d,height=%d)\n",
Shape_getX(&r1.super), Shape_getY(&r1.super),
r1.width, r1.height);
printf("Rect r2(x=%d,y=%d,width=%d,height=%d)\n",
Shape_getX(&r2.super), Shape_getY(&r2.super),
r2.width, r2.height);
// 注意,这里有两种方式,一是强转类型,二是直接使用成员地址
Shape_moveBy((Shape *)&r1, -2, 3);
Shape_moveBy(&r2.super, 2, -1);
printf("Rect r1(x=%d,y=%d,width=%d,height=%d)\n",
Shape_getX(&r1.super), Shape_getY(&r1.super),
r1.width, r1.height);
printf("Rect r2(x=%d,y=%d,width=%d,height=%d)\n",
Shape_getX(&r2.super), Shape_getY(&r2.super),
r2.width, r2.height);
return 0;
}
多态
想要实现多态,我们可以仿照C++的方式,先定义一个虚表结构体,里面存储指向虚函数的指针。同时在类的结构体中加入一个虚表指针,在构造函数中初始化虚表指针。
struct AnimalVtbl;
struct Animal {
//虚表指针
const struct AnimalVtbl* vptr;
int age;
};
//Animal的虚表
struct AnimalVtbl {
void (*eat)(const Animal* const obj);
void (*sound)(const Animal* const obj);
};
//以下两个为虚函数
void Animal_eat(const Animal* const obj) {
std:cout << "Animal eat ..." << std::endl;
}
void Animal_sound(const Animal* const obj) {
std::cout << "Animal sound like ..." << std::endl;
}
//对象的虚表指针应该在构造函数中初始化
void Animal_ctor(Animal* const obj, int age_) {
static AnimalVtbl const vtbl = {
&Animal_eat,
&Animal_sound,
};
obj->vptr = &vtbl;
obj->age = age_;
}
void Animal_dtor(Animal* const obj) {
obj->age = 0;
}
int main() {
Animal a;
Animal_ctor(&a, 18);
a.vptr->eat(&a);
a.vptr->sound(&a);
}