C面向对象
曾经谈到面相对象,就是C++,之后最多加上Java。后来,读过的书上说,面向对象其实只是一种设计方式——万事即对象,而不在乎用的是什么语言。C语言,一样也可以做面向对象设计,有时候还更自由,没有C++的各种隐藏的坑。这篇笔记就具体说说如何用C来做面向对象。
开始之前,用一篇网上收录的笔记作为入门:用C语言写面向的对象是一种什么样的体验。
封装
对类结构的封装,C里面用struct
,相比C++缺少的就是:
- 构造函数和析构函数
- private & protect
/** Class definition of BaseLed. * The concrete LED class should inherit from this class. */typedef struct BaseLed{ uint8_t id; /** * Below 4 members are used to record the LED blinking state. */ LedState state; uint16_t onTime; uint16_t offTime; uint16_t blinkCount; /** * Public function members. */ bool (*init)(struct BaseLed*, uint8_t); //!< Init hardware if needed void (*switchit)(struct BaseLed*, LedState); //!< Turn on/off void (*set_brightness)(struct BaseLed*, uint8_t); //!< Set LED's brightness level}BaseLed;
上面的例子,上半部分是成员变量,下半部分是成员函数。
构造函数&析构函数
C++中,构造函数在以下情况中调用:
- 比如你在main里面声明了一个类A..那么~A()会在main结束时调用
- 如果在自定义的函数f()里面声明了一个A 函数f结束的时候就会调用~A()
- 或者你delete 指向A的指针..
- 或者显式的调用析构函数
C++各种隐藏的坑,参考网页那些被C++默默地声明和调用的函数
在C面向对象里面,就没有这种问题,因为C语言根本不会为你生成隐藏的函数,也不会帮你调用构造函数,分配内存之类的,也没有虚函数表(VTable)。所有这一切都得自己亲力亲为。不过这样也好,所见即所得嘛。
// ClassA.htypedef struct _ClassA{ int a; void (*funcA)(struct _ClassA*, int);}ClassA;ClassA* newA(int);void deleteA(ClassA*)// ClassA.c#include <ClassA.h>void funcA(ClassA* thisObj, int a){}ClassA* newA(int a){ ClassA* Aobj = malloc(sizeof(ClassA)); Aobj->funcA = funcA; Aobj->a = a;}void deleteA(ClassA* obj){ free(obj);}// main.c#include <ClassA.h>void main(){ ClassA* aobj = newA(0);}
在头文件中定义结构体,在.c文件中定义成员函数,在需要调用的地方include对应的头文件。注意,当main
函数和ClassA定义在不同的lib中时,头文件中的newA, deleteA需要定义为extern。
访问控制——private & protect
上面的例子是最基本的封装定义。C++中最有趣的是访问控制,即隐藏具体实现,而只暴露接口。
private
怎么做到private呢?用结构体把private包起来。
// ClassA.c#include <ClassA_Private.h>#include <ClassA.h>ClassA* newA(int a){ ClassA* pA = malloc(sizeof(ClassA)); pA->private_data = malloc(sizeof(ClassA_Private)); ClassA_Private* pPrivate = (ClassA_Private*)(pA->private_data); pPrivate->private_a = a;}...
// ClassA.htypedef struct _ClassA{ int a; void (*funcA)(struct _ClassA*, int); void* private_data; // void pointer hide all details}ClassA;
// ClassA_Private.hstruct _ClassA;typedef _ClassA_Private{ int private_a; void (*private_funcA)(struct _ClassA*, int);}ClassA_Private
// main.c#include <ClassA.h>void main(){ ClassA* obj = newA(0); // Below line will bring compile error. // obj->private_data.private_funcA}
将不想暴露的数据放在一个结构体中,类定义的头文件包含该头文件。对于想隐藏的客户代码,不暴露该头文件,则其不能引用该private代码。
protect
protect数据是只能暴露给子类和友元,关于友元,参考【C++基础之十】友元函数和友元类
在C里面,就没这么多花头了,“对于想隐藏的客户代码,不暴露该头文件”。
// ChildClassA.h#include <BaseClassA.h>#include <BaseClassA_Private.h>typedef struct _ChildClassA{ BaseClassA parent; ...}// FriendClassA.h#include <BaseClassA.h>#include <BaseClassA_Private.h>typedef struct _ChildClassA{ BaseClassA parent; ...}// main.c#include <BaseClassA.h>
继承
上面已经看到继承的基本代码了,但是为了实现标准的继承我们还要做的更多。
// BaseClassAtypedef struct BaseClass{ int a; void (*funcA)(struct BaseClassA*, int);}BaseClassA;// DerivedClassB#include <BaseClassA.h>typedef struct DerivedClassB{ BaseClassA parent; int special; void (*funcA)(struct DerivedClassB*, int); void (*funcB)(struct DerivedClassB*, int);}DerivedClassB;DerivedClassB* NewB(){ DerivedClassB* newB = malloc(sizeof(DerivedClassB)); newB->parent.funcA = funcA; newB->funcA = funcA; return newB;}
子类包含父类的实例,这样就实现了继承。子类同名函数重写了父类同名函数。为下面多态做好准备。
多态
接着上面的例子
BaseClassA* pBase = (BaseClassA*)NewB();pBase->funcA(); // 调用的是子类构造函数中赋予的函数,这就是多态了