c印记(二):lw_oopc简介

目录

    • 目录
    • 1 lw_oopc简介
    • 2 lw_oopc 宏定义介绍
      • 2_1 基础宏部分
        • 2_1_1 CLASS
        • 2_1_2 CTOREND_CTOR
        • 2_1_3 DTOREND_DTOR
        • 2_1_4 FUNCTION_SETTING
        • 2_1_5 基础宏应用实例
      • 2_2 ABS_CLASS扩展
        • 2_2_1 概述
        • 2_2_2 例子
      • 2_3 INTERFACE扩展
        • 2_3_1 概述
        • 2_3_2 例子
    • 3 总结

1、 lw_oopc简介

在上一篇中简单的提了一下面向对象以及c语言实现面向对象,写得一塌糊涂,这里就不管了,本篇主要介绍lw_oopc这个东东。

lw_oopc是由高焕堂及其MISOO团队创作的(在《UML+OOPC嵌入式C语言开发精讲》一书中有相关介绍),本文说的lw_oopc并非《UML+OOPC嵌入式C语言开发精讲》中原版的lw_oopc,而是source forge上下 载的lw_oopc(在原版的基础上有经过优化和功能增加,如增加了ABS_CLASS(虚基类),且是以LGPL协议开源),其作者是 金永华,在其readme文档中有对两者(原版和这个1.2版本)的对比:

原有高焕堂先生及其MISOO团队创作的宏(总共6个宏),清单如下:

高焕堂及MISOO创作的宏 是否存在问题? 是否修改?
INTERFACE 没有问题
CLASS 无法支持继承
CTOR 对申请不到内存的情况未保护
END_CTOR 没有问题
FUNCTION_SETTING 没有问题
IMPLEMENTS 没有问题

为了更好的支持面向对象以及面向接口编程,我(金永华)增加了14个宏:

金永华创作的宏 创作目的(为了解决什么问题?)
DTOR 为了支持析构函数的概念
END_DTOR
ABS_CLASS 为了支持抽象类的概念
ABS_CTOR 为了支持可继承的抽象类的构造函数
END_ABS_CTOR
EXTENDS 为了让熟悉Java的人容易理解(与IMPLEMENTS宏等同)
SUPER_CTOR 为了支持子类调用父类的构造函数
SUPER_PTR 为了支持向上转型
SUPER_PTR_2
SUPER_PTR_3
SUB_PTR 为了支持向下转型
SUB_PTR_2
SUB_PTR_3
INHERIT_FROM 为了支持访问直接父类的数据成员

2、 lw_oopc 宏定义介绍

由于篇幅所限,并一定会每个宏都做介绍,因为这些宏都比较简单,所以并不会过多的介绍宏本身,更多的是以使用角度来讲。

2_1 基础宏部分

2_1_1 CLASS

首先咱就来说说这个最基本的关键字(CLASS),在lw_oopc中CLASS这个宏定义如下:

#define CLASS(type)        /*其中的type为所要定义的类名*/                      \
typedef struct type type;        /*使用typedef定义一个名为‘type’的类名*/       \
type* type##_new(lw_oopc_file_line_params); /*创建对象的函数,类似c++中的new*/ \
void type##_ctor(type* t);       /*类的构造函数声明*/                         \
int type##_dtor(type* t);        /*类的析构函数声明*/                         \
void type##_delete(type* t);     /*释放对象的函数,类似c++中的delete*/         \
struct type    /*名为‘type’的结构体定义*/

也以上一篇中Person类为例,定义如下:

CLASS(Person)
{
    void (*Init)(struct Person* pPerson, int age, const char* name);
    void (*sayHello)(struct Person* pPerson);
    int mAge;
    char* mName;
};

与上一篇中使用c语言的结构体来模拟类相比,这里就少了一个Uninit方法,因为这个方法的行为将在Person的析构函数中进行。至于ABS_CLASS和INTERFACE宏与CLASS基本类似,只是没有type##_new以及type##_delete方法。

2_1_2 CTOR/END_CTOR

这组宏用于定义类的构造函数,具体宏定义如下:

#define CTOR(type)                                      \
    type* type##_new() { /*实现创建对象的函数*/           \
    struct type *cthis;                                 \
    cthis = (struct type*)malloc(sizeof(struct type)); /*为对象(即struct)分配内存*/ \
    if(!cthis)                                          \
    {                                                   \
        return 0;                                       \
    }                                                   \
    type##_ctor(cthis);/*调用类的构造函数*/               \
    return cthis;                                       \
}                                                       \
                                                        \
void type##_ctor(type* cthis) {/*定义类的构造函数*/

#define END_CTOR    } /*类构造函数的结束大括号*/

CTOR宏中的malloc可以使用其他的接口函数代替,以便记录一些debug信息,在lw_oopc中在memory leak检测的时 候就会使用:
lw_oopc_malloc(sizeof(struct type), #type, file, line);
代替malloc,他会记录类的名字,源码的文件名,以及在文件中的行号。

2_1_3 DTOR/END_DTOR

这组宏是在金永华创作的lw_oopc当中新增的,定义类的析构函数,与CTOR相对应:

#define DTOR(type)                                        \
void type##_delete(type* cthis) /*实现释放对象的函数*/      \
{                                                         \
    if(type##_dtor(cthis)) /*调用类的析构函数*/             \
    {                                                     \
            lw_oopc_free(cthis);/*释放对象的内存*/          \
    }                                                     \
}                                                         \
int type##_dtor(type* cthis) /*定义类的析构函数*/           \
{

#define END_DTOR } /*类析构函数结束大括号*/

2_1_4 FUNCTION_SETTING

这个宏用于注册成员函数,它的定义也比较简单,基本上就是一个赋值语句(当然,如果有需要的话也可以丰富这个宏的内容,让其功能更强大):

#define FUNCTION_SETTING(f1, f2) cthis->f1=f2;/*将成员函数赋值给struct中的函数指针*/

2_1_5 基础宏应用实例

以上几个定义和注册函数的使用方式如下:

/* 声明Person类 */
CLASS(Person)
{
    void (*Init)(struct Person* pPerson, int age, const char* name);
    void (*sayHello)(struct Person* pPerson);
    int mAge;
    char* mName;
};

/*定义成员函数,为了封装性,建议在.c档案中定义static类型的函数*/    
static void init(struct Person* pPerson, int age, const char* name)
{
     pPerson->mAge = age;
     pPerson->mName = name;/*为了简单表示,这里不去关心字符串内存问题*/
}


static void sayHello(struct Person* pPerson)
{
   printf("hello, my name is:%s and i'm %d years old\n", pPerson->mName, pPerson->mAge);
}

/*定义构造函数*/
CTOR(Person)
cthis->mAge = 0;
cthis->mName = NULL; /*初始化成员函数*/
FUNCTION_SETTING(Init, init);
FUNCTION_SETTING(sayHello, sayHello); /*注册成员函数*/
END_CTOR

/*定义析构函数*/
DTOR(Person)
//todo release member variable
return lw_oopc_true; //如果需要释放对象就分会true,反之返回false(这样可以实现引用计数之类的应用)
END_DTOR

/*具体使用*/
int main(int argc, char* argv[])
{
    Person* pPerson = Person_new(); /*创建Person的对象实例*/
    pPerson->Init(pPerson, 12, "LiLei");/* 初始化对象 */
    pPerson->sayHello(pPerson);
    Person_delete(pPerson);
    return 0;
}

2_2 ABS_CLASS扩展

2_2_1 概述

ABS_CLASS宏对应于c++中的虚基类(因为只是c语言的宏,所以无法从语法规则上去约束其只是虚基类,也就是说具体使用的时候也可能将其作为普通的基类(即所有成员函数都有实现))。这个宏的作用就是可以让c语言实现继承以及多态。当然这里也有不少缺陷和约束,比如因为不是语法规则上的约束,所以无法在编译阶段就知道是不是所有成员函数都有对应的实现。

其宏定义和CLASS差不多,只是没有type##_new以及type##_delete方,所以这里就不在将其列出来了,下面就以一个例子来说明其使用方式。

2_2_2 例子

这里以lw_oopc的demo中关于动物的例子(会有适当的修改,详情请到lw_oopc的源码包中去查看),动物是一个基类,而狗,鱼等都属于动物,是动物的子类(is-a关系):

#include "lw_oopc.h"
#include 

ABS_CLASS(Animal)/*声明动物 基类*/
{
    char name[128];     // 动物的昵称(假设小于128个字符)
    int age;            // 动物的年龄

    void (*setName)(Animal* t, const char* name);   // 设置动物的昵称
    void (*setAge)(Animal* t, int age);             // 设置动物的年龄 
    void (*sayHello)(Animal* t);                    // 动物打招呼
    void (*init)(Animal* t, const char* name, int age); // 初始化昵称和年龄
};

CLASS(Fish) /*声明 鱼 类*/
{
    EXTENDS(Animal);        // 继承Animal抽象类

    void (*init)(Fish* t, const char* name, int age);
};

CLASS(Dog) /*声明 狗 类*/
{
    EXTENDS(Animal);        // 继承Animal抽象类

    void(*init)(Dog* t, const char* name, int age);
};

/
/

/* 设置动物的昵称 */
void Animal_setName(Animal* t, const char* name)
{
    // 这里假定name小于128个字符,为简化示例代码,不做保护(产品代码中不要这样写)
    strcpy(t->name, name);
}
/* 设置动物的年龄 */
void Animal_setAge(Animal* t, int age)
{
    t->age = age;
}
/* 动物和我们打招呼 */
void Animal_sayHello(Animal* t)
{
    printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);
}
/* 初始化动物的昵称和年龄 */
void Animal_init(Animal* t, const char* name, int age)
{
    t->setName(t, name);
    t->setAge(t, age);
}

ABS_CTOR(Animal) /*定义 动物 类的构造函数,并注册成员函数*/
FUNCTION_SETTING(setName, Animal_setName);
FUNCTION_SETTING(setAge, Animal_setAge);
FUNCTION_SETTING(sayHello, Animal_sayHello);
FUNCTION_SETTING(init, Animal_init);
END_ABS_CTOR

/

/* 初始化鱼的昵称和年龄 */
void Fish_init(Fish* t, const char* name, int age)
{
    Animal* animal = SUPER_PTR(t, Animal);
    animal->setName(animal, name);
    animal->setAge(animal, age);
}


CTOR(Fish)/*定义 鱼 类的构造函数,并注册成员函数*/
SUPER_CTOR(Animal);
FUNCTION_SETTING(init, Fish_init);
END_CTOR



/* 初始化狗的昵称和年龄 */
void Dog_init(Dog* t, const char* name, int age)
{
    Animal* animal = SUPER_PTR(t, Animal);
    animal->setName(animal, name);
    animal->setAge(animal, age);
}

CTOR(Dog)/*定义 狗 类的构造函数,并注册成员函数*/
SUPER_CTOR(Animal);
FUNCTION_SETTING(init, Dog_init);
END_CTOR

///
///

int main(int argc, char* argv[])
{    
    Fish* fish = Fish_new();    // 创建鱼对象
    Dog* dog = Dog_new();       // 创建狗对象
    Animal* animal = NULL;

    // 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
    fish->init(fish, "小鲤鱼", 1);          

    // 初始化狗对象的昵称为:牧羊犬,年龄为:2岁
    dog->init(dog, "牧羊犬", 2); 

    // 将fish指针转型为Animal类型指针
    animal = SUPER_PTR(fish, Animal);       
    animal->sayHello();

    // 将dog指针转型为Animal类型指针
    animal = SUPER_PTR(dog, Animal);
    animal->sayHello(); 

    lw_oopc_delete(fish);
    lw_oopc_delete(dog);

    return 0;
}

从上面的例子可以看出,c模拟面向对象毕竟不是原生支持,必然会有很多缺陷和约束,比如这个狗类中继承动物类的sayHello函数,就无法使用dog对象去调用,必须将其转换为animal对象方能调用。

这里的继承其实可以用在c的模块开发当中,ABS_CLASS定义模块的接口,CLASS再定义各种子模块,比如设计一个stream模块(各种流操作),就可以使用ABS_CLASS定义出stream模块的对外接口,以及一些stream模块内部各个子模块可共用的接口,然后在stream模块就可以使用CLASS去定义一些具体的子模块,如本地文件CLASS(FileStream),网络文件CLASS(HttpStream)等等。

2_3 INTERFACE扩展

2_3_1 概述

这个概念应该源自java中的interface,以interface 关键字定义一套接口方法(这里只有方法的声明,没有实现 ),然后再在具体的类中去实现这些接口方法。

那么问题就来了,既然有了ABS_CLASS,那还要INTERFACE干嘛呢? 就以lw_oopc的demo中的例子来说,动物,除了有呼吸,吃东西等行为之外,还有一个共同的特征就是“移动”。如 果以ABS_CLASS的方式实现的话,就会在Animal类中定义一个move方法。

那是不是只有动物才会“移动”呢? 显然不是,最常见的比如汽车,也是会移动的物体,当它却不是动物,如果以 ABS_CLASS方式实现,那么就需要在Animal和Car类中都声明move方法,为何要如此呢? 这是因为动物和汽车是不 同的类别。

当然这里还有另一种实现方式——INTERFACE,move是一种抽象的行为,谁有这种行为,就去实现这个INTERFACE,汽车能够移动,那么汽车类就去实现汽车的移动方式,动物能够移动,那动物类就去实现动物的移动方式。

2_3_2 例子

这里还是以lw_oopc中的例子来说明 INTERFACE的具体应用:

#include "lw_oopc.h"
#include 

INTERFACE(IMoveable)
{
    void (*move)(IMoveable* t);     // Move行为
};

/*这里为了简单,就不用Animal基类了*/
CLASS(Fish)
{
    IMPLEMENTS(IMoveable);  // 实现IMoveable接口
    void (*sayHello)(Fish* t);                    // 动物打招呼
    void (*init)(Fish* t, const char* name, int age);

    char name[128];     // 动物的昵称(假设小于128个字符)
    int age;            // 动物的年龄
};

///
///
/* 鱼的移动行为 */
void Fish_move(IMoveable* t)
{
    printf("鱼在水里游!\n");
}
/* 初始化鱼的昵称和年龄 */
void Fish_init(Fish* t, const char* name, int age)
{
     // 这里假定name小于128个字符,为简化示例代码,不做保护(产品代码中不要这样写)
    strcpy(t->name, name);
    t->age = age;
}

/* 动物和我们打招呼 */
void Fish_sayHello(Fish* t)
{
    printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);
}

CTOR(Fish)/*定义 鱼 类的构造函数,并注册成员函数*/
FUNCTION_SETTING(IMoveable.move, Fish_move);
FUNCTION_SETTING(init, Fish_init);
FUNCTION_SETTING(sayHello, Fish_sayHello);
END_CTOR
///
///
int main(int argc, char* argv[])
{    
    Fish* fish = Fish_new();    // 创建鱼对象
    IMoveable* moveObj = NULL;

    // 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
    fish->init(fish, "小鲤鱼", 1);          

    //鱼打招呼
    fish->sayHello();

    // 将fish指针转型为IMoveable接口类型指针
    moveObj = SUPER_PTR(fish, IMoveable);
    //鱼移动
    moveobj->move();

    lw_oopc_delete(fish);

    return 0;
}

3、 总结

从以上简介可以看出,虽然其可以模拟面向对象编程,但显然其中有不少缺陷和约束。而且这些宏的功能虽然基本的都有了,用于简单的学习是完全足够,但如果要用于实际的开发项目,可能就还需要更多的功能扩展和优化(这个下一章会有相关说明),现在我们来总结一下lw_oopc宏的一些优缺点(都是寡人的门户之见,并不一定正确,如有不同看法,欢迎拍砖):
- 优点
1. 比较简洁,基本上一看就懂,学习曲线比较平缓;
2. 能在一定程度上实现面向对象编程;
3. 有利于功能模块以及子模块的划分和隔离。
- 缺点:
1. 功能还不完善,需要进一步增强和优化才能更好的用于实际项目;
2. 由于是使用的宏定义,会使用预处理机制,所以在这些宏定义中就无法在编译时进行类型检测;
3. 由于是模拟的面向对象编程,所以没有原生支持的编程语言使用起来方便;
4. 无法在编译时就检测成员函数是否已经实现,如未实现,就会在运行时产生segmentation fault错误。
5. 阅读不方便,如source insight这样的工具,无法判断以CLASS等宏定义的结构体,在阅读以 及代码跳转查看的时候不太方便

你可能感兴趣的:(c/c++)