轻量级的面向对象C语言编程框架LW_OOPC介绍
金永华、陈国栋
2010/03/02
摘要:
本文介绍一种轻量级的面向对象的C语言编程框架:LW_OOPC。LW_OOPC是Light-Weight Object-Oriented Programming in(with) C的缩写,总共一个.h文件,20个宏,约130行代码,非常的轻量级,但却很好的支持了很多面向对象的特性,比如继承、多态,可以优美的实现面向接口编程。这个框架系由台湾的高焕堂先生以及他的MISOO团队首创,之后由我继续改进优化,最后,经高焕堂同意以LGPL协议开源(开源网址参见后文)。
用C语言实现OO?我没听错吗?这听起来真是太疯狂了!… 大家都知道,C++支持了面向对象和面向泛型编程,比C要更强大些。那么,为什么要在C语言中实践面向对象呢?为什么不直接使用C++呢?
为什么要用面向对象?
面向过程方式开发的系统,代码复杂,耦合性强,难以维护,随着我们所要解决的问题越来越复杂,代码也变得越来越复杂,越来越难以掌控,而面向对象改变了程序员的思维方式,以更加符合客观世界的方式来认识世界,通过合理的运用抽象、封装、继承和多态,更好的组织程序,从而很好地应对这种复杂性。
为什么不直接使用C++?
C和C++之争由来已久,可能要持续到它们中的一种去世^_^。C语言以其简洁明快,功能强大的特点,深得开发人员的喜爱,尤其是在嵌入式开发领域,C语言更是占据了绝对老大的地位。在我看来,语言只是工具,作为程序员,我们要做的是:选择合适的语言,解决恰当的问题。我们要尊重事实,考虑开发环境(软硬件环境),考虑团队成员的水平,从商用工程的角度讲,选择团队成员擅长的语言进行开发,风险要小很多。
一些从Java/C#转到C的程序员们,无法从面向对象切换到面向过程,但又必须与C语言同事们在遗留的C系统上开发软件,他们有时会非常困惑:C语言是面向过程的编程语言,如何实践面向对象,甚至面向接口编程呢?此时,就非常需要在C语言中实现面向对象的手段,而LW_OOPC正是应对这一难题的解决之道。
LW_OOPC是什么?
简而言之:LW_OOPC是一套C语言的宏,总共1个.h文件(如果需要内存泄漏检测支持以及调试打印支持,那么还需要1个.c文件(lw_oopc.c,约145行)),20个宏,约130行代码。LW_OOPC是一种C语言编程框架,用于支持在C语言中进行面向对象编程。
LW_OOPC宏介绍
下面,先通过一个简单的示例来展示LW_OOPC这套宏的使用方法。我们要创建这样一些对象:动物(Animal),鱼(Fish),狗(Dog),车子(Car)。显然,鱼和狗都属于动物,都会动,车子也会动,但是车子不是动物。会动是这些对象的共同特征,但是,显然它们不属于一个家族。因此,我们首先考虑抽象出一个接口(IMoveable),以描述会动这一行为特征:
INTERFACE(IMoveable)
{
void (*move)(IMoveable* t); // Move行为
};
INTERFACE宏用于定义接口,其成员(方法)均是函数指针类型。
然后,我们分析Animal,它应该是抽象类还是接口呢?动物都会吃,都需要呼吸,如果仅仅考虑这两个特征,显然可以把Animal定为接口。不过,这里,为了展示抽象类在LW_OOPC中如何应用。我们让Animal拥有昵称和年龄属性,并且,让动物和我们打招呼(sayHello方法),但,我们不允许用户直接创建Animal对象,所以,这里把Animal定为抽象类:
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 (*eat)(Animal* t); // 动物都会吃(抽象方法,由子类实现)
void (*breathe)(Animal* t); // 动物都会呼吸(抽象方法,由子类实现)
void (*init)(Animal* t, const char* name, int age); // 初始化昵称和年龄
};
ABS_CLASS宏用于定义抽象类,允许有成员属性。代码的含义参见代码注释。
紧接着,我们来定义Fish和Dog类,它们都继承动物,然后还实现了IMoveable接口:
CLASS(Fish)
{
EXTENDS(Animal); // 继承Animal抽象类
IMPLEMENTS(IMoveable); // 实现IMoveable接口
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
};
CLASS(Dog)
{
EXTENDS(Animal); // 继承Animal抽象类
IMPLEMENTS(IMoveable); // 实现IMoveable接口
void(*init)(Dog* t, const char* name, int age); // 初始化昵称和年龄
};
为了让Fish对象或Dog对象在创建之后,能够很方便地初始化昵称和年龄,Fish和Dog类均提供了init方法。
下面,我们来定义Car,车子不是动物,但可以Move,因此,让Car实现IMoveable 接口即可:
CLASS(Car)
{
IMPLEMENTS(IMoveable); // 实现IMoveable接口(车子不是动物,但可以Move)
};
接口,抽象类,具体类的定义都已经完成了。下面,我们开始实现它们。接口是不需要实现的,所以IMoveable没有对应的实现代码。Animal是抽象动物接口,是半成品,所以需要提供半成品的实现:
/* 设置动物的昵称*/
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
这里出现了几个新的宏,我们逐个进行讲解。ABS_CTOR表示抽象类的定义开始,ABS_CTOR(Animal)的含义是Animal抽象类的“构造函数”开始。在C语言里边其实是没有C++中的构造函数的概念的。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct实例)分配内存,然后,紧接着要为结构体中的函数指针成员赋值,这一过程,也可以称为函数绑定(有点类似C++中的动态联编)。函数绑定的过程由FUNCTION_SETTING宏来完成。
对于Fish和Dog类的实现,与Animal基本上是类似的,除了将ABS_CTOR换成了CTOR,直接参见代码:
/* 鱼的吃行为 */
void Fish_eat(Animal* t)
{
printf("鱼吃水草!/n");
}
/* 鱼的呼吸行为 */
void Fish_breathe(Animal* t)
{
printf("鱼用鳃呼吸!/n");
}
/* 鱼的移动行为 */
void Fish_move(IMoveable* t)
{
printf("鱼在水里游!/n");
}
/* 初始化鱼的昵称和年龄 */
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(Animal.eat, Fish_eat);
FUNCTION_SETTING(Animal.breathe, Fish_breathe);
FUNCTION_SETTING(IMoveable.move, Fish_move);
FUNCTION_SETTING(init, Fish_init);
END_CTOR
上面是Fish的实现,下面看Dog的实现:
/* 狗的吃行为 */
void Dog_eat(Animal* t)
{
printf("狗吃骨头!/n");
}
/* 狗的呼吸行为 */
void Dog_breathe(Animal* t)
{
printf("狗用肺呼吸!/n");
}
/* 狗的移动行为 */
void Dog_move(IMoveable* t)
{
printf("狗在地上跑!/n");
}
/* 初始化狗的昵称和年龄 */
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(Animal.eat, Dog_eat);
FUNCTION_SETTING(Animal.breathe, Dog_breathe);
FUNCTION_SETTING(IMoveable.move, Dog_move);
FUNCTION_SETTING(init, Dog_init);
END_CTOR
细心的朋友可能已经注意到了,这里又有一个陌生的宏:SUPER_CTOR未介绍。这个宏是提供给子类用的,用于调用其直接父类的构造函数(类似Java语言中的super()调用,在这里,其实质是要先调用父类的函数绑定过程,再调用自身的函数绑定过程),类似Java那样,SUPER_CTOR如果要出现,需要是ABS_CTOR或者CTOR下面紧跟的第一条语句。
最后,我们把Car类也实现了:
void Car_move(IMoveable* t)
{
printf("汽车在开动!/n");
}
CTOR(Car)
FUNCTION_SETTING(IMoveable.move, Car_move);
END_CTOR
下面,我们实现main方法,以展示LW_OOPC的威力J:
#include "animal.h"
int main()
{
Fish* fish = Fish_new(); // 创建鱼对象
Dog* dog = Dog_new(); // 创建狗对象
Car* car = Car_new(); // 创建车子对象
Animal* animals[2] = { 0 }; // 初始化动物容器(这里是Animal指针数组)
IMoveable* moveObjs[3] = { 0 }; // 初始化可移动物体容器(这里是IMoveable指针数组)
int i = 0; // i和j是循环变量
int j = 0;
// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
fish->init(fish, "小鲤鱼", 1);
// 将fish指针转型为Animal类型指针,并赋值给animals数组的第一个成员
animals[0] = SUPER_PTR(fish, Animal);
// 初始化狗对象的昵称为:牧羊犬,年龄为:2岁
dog->init(dog, "牧羊犬", 2);
// 将dog指针转型为Animal类型指针,并赋值给animals数组的第二个成员
animals[1] = SUPER_PTR(dog, Animal);
// 将fish指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第一个成员
moveObjs[0] = SUPER_PTR(fish, IMoveable);
// 将dog指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第二个成员
moveObjs[1] = SUPER_PTR(dog, IMoveable);
// 将car指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第三个成员
moveObjs[2] = SUPER_PTR(car, IMoveable);
// 循环打印动物容器内的动物信息
for(i=0; i<2; i++)
{
Animal* animal = animals[i];
animal->eat(animal);
animal->breathe(animal);
animal->sayHello(animal);
}
// 循环打印可移动物体容器内的可移动物体移动方式的信息
for(j=0; j<3; j++)
{
IMoveable* moveObj = moveObjs[j];
moveObj->move(moveObj);
}
lw_oopc_delete(fish);
lw_oopc_delete(dog);
lw_oopc_delete(car);
return 0;
}
从上边的代码中,我们惊喜地发现,在C语言中,借助LW_OOPC,我们实现了将不同的动物(Fish和Dog对象)装入Animal容器,然后可以用完全相同的方式调用Animal的方法(比如eat和breathe方法),而实际调用的是具体的实现类(Fish和Dog)的对应方法。这正是面向对象中的多态的概念。同样,我们可以将Fish对象,Dog对象,以及Car对象均视为可移动物体,均装入IMoveable容器,然后用完全相同的方式调用IMoveable接口的move方法。看到了吗?借助LW_OOPC,在C语言下我们竟然可以轻松地实现面向对象和面向接口编程!
下面,再举一个稍微复杂的例子,它的覆盖面是足够全面的,足以一瞥面向对象编程的3个要素:数据抽象、继承和多态。通过这个例子,我们期望展现出LW_OOPC在遭遇问题本身比较复杂的情形下,是如何从容应对的,以加深读者对LW_OOPC的认识。(备注:该问题来自《C++沉思录》第八章的例子,有兴趣的读者可以对照参阅)
问题描述:
此程序涉及的内容是用来表示算术表达式的树。例如,表达式(-5)*(3+4)对应的树为:
一个表达式树包括代表常数、一元运算符和二元运算符的节点。这样的树结构在编译器和计算器程序中都可能用到。
我们希望能通过调用合适的函数来创建这样的树,然后打印该树的完整括号化形式。例如,我们希望
#include "stdio.h"
#include "Expr.h"
int main()
{
Expr* expr1 = Expr_new();
Expr* expr2 = Expr_new();
Expr* expr3 = Expr_new();
Expr* expr = Expr_new();
expr1->initUnaryX(expr1, "-", 5);
expr2->initBinaryX(expr2, "+", 3, 4);
expr3->initBinary(expr3, "*", expr1, expr2);
expr->initBinary(expr, "*", expr3, expr3);
expr3->print(expr3);
printf("/n");
expr->print(expr);
printf("/n");
Expr_delete(expr);
Expr_delete(expr3);
Expr_delete(expr2);
Expr_delete(expr1);
return 0;
}
打印
((-5)*(3+4))
(((-5)*(3+4))*((-5)*(3+4)))
作为输出。此外,我们不想为这些表达式的表示形式操心,更不想关心有关它们内存分配和回收的事宜。
这个程序所做的事情在很多需要处理复杂输入的大型程序中是很典型的,例如编译器、编辑器、CAD/CAM系统等。此类程序中通常要花费很大的精力来处理类似树、图和类似的数据结构。这些程序的开发者永远需要面对诸如内存分配、灵活性和效率之类的问题。面向对象技术可以把这些问题局部化,从而确保今后发生的一系列变化不会要求整个程序中的其他各个部分随之做相应调整。
解决方案:
通过考查这个树结构,会发现这里有3种节点。一种表示整数表达式,包含一个整数值,无子节点。另外两个分别表示一元表达式和二元表达式,包含一个操作符,分别有一个或两个子节点。我们希望打印各种节点,但是具体方式需要视要打印节点的类型而定。这就是动态绑定的用武之地了:我们可以定义一个虚函数(print)来指明应当如何打印各种节点。动态绑定将会负责在运行时基于打印节点的实际类型调用正确的函数。
首先,我们抽象出“节点”的概念,抽象类的名字定为Expr_node,它提供了打印的抽象接口,所有的实际节点类型均从它派生:
ABS_CLASS(Expr_node)
{
void (*print)(Expr_node* t);
};
具体类的情形怎样?这些具体类型中最简单的一类是包含一个整数,没有子节点的节点:
CLASS(Int_node)
{
EXTENDS(Expr_node);
int n;
void (*init)(Int_node* t, int k);
};
其他类型又如何呢?每个类中都必须存储一个操作符(这倒简单,本文中假定操作符最长不超过2个字符,所以,可以用长度为3的字符数组来保存),但是如何存储子节点呢?在运行时之前,我们并不知道子节点的类型会是什么,所以我们不能按值存储子节点,必须存储指针。这样,一元和二元节点类如下所示:
CLASS(Unary_node)
{
EXTENDS(Expr_node);
char op[3]; //假设操作符最长不超过2个字符
Expr_node* opnd;
void (*init)(Unary_node* t, const char* a, Expr_node* b);
};
CLASS(Binary_node)
{
EXTENDS(Expr_node);
char op[3]; //假设操作符最长不超过2个字符
Expr_node* left;
Expr_node* right;
void (*init)(Binary_node* t, const char* a, Expr_node* b,
Expr_node * c);
};
这个设计方案可以用,不过有一个问题。用户要处理的不是值,而是指针,所以必须记住分配和释放对象。例如,我们需要这么创建表达式树:
Int_node* int_node1 = Int_node_new();
Int_node* int_node2 = Int_node_new();
Int_node* int_node3 = Int_node_new();
Unary_node* unary_node = Unary_node_new();
Binary_node* binary_node1 = Binary_node_new();
Binary_node* binary_node = Binary_node_new();
int_node1->init(int_node1, 5);
int_node2->init(int_node2, 3);
int_node3->init(int_node3, 4);
unary_node->init(unary_node, "-", int_node1);
binary_node1->init(binary_node1, "+", int_node2, int_node3);
binary_node->init(binary_node, "*", unary_node, binary_node1);
lw_oopc_delete(int_node1);
…… // 删除创建的其他节点
也就是说,我们需要去关心每一个节点的创建和释放。我们不仅把内存管理这类烦心事推给了用户,而且对用户来说也没有什么方便的办法来处理这些事情。我们得好好想想办法了。
这里,提供一种解决内存管理问题的思路:引用计数,这里是针对指针,对指针的状况进行计数,对象创建的时候,引用计数为1,凡是指针被赋值了,该指针所指对象的引用计数就自增一,每次指针要释放,都先检查对象的引用计数,让引用计数自减一,如果引用计数为0,则释放该对象。
另外,原先的设计不够高层,用户只能直接针对节点进行操作,没有提供操作子树的概念(这也是用户代码之所以复杂的原因之一),我们发现,通过提供子树的概念,我们不但能够隐藏Expr_node继承层次,而且,对于每一个节点,我们具备了操纵左子树和右子树的能力(原来只能操作左子节点和右子节点)。而这种功能增强完全是建立在面向对象的机制之上,我们并没有引入耦合,在非常自然和轻松的情形下,我们获得了更好的软件组件之间协作的能力,这正是面向对象的魅力所在。
这里,我们把子树的概念用类Expr来表示,由于子树此时成了Expr_node具体类的成员,同样,左右子树在Expr_node中同样是以指针的方式保存,所以,对Expr也需要进行引用计数,代码直接贴上来,细节解说参见注释:
// expr.h
#ifndef EXPR_H_INCLUDED_
#define EXPR_H_INCLUDED_
#include "lw_oopc.h"
// 表达式节点
ABS_CLASS(Expr_node)
{
int use; // 引用计数
void (*print)(Expr_node* t); // 打印表达式节点
void (*finalize)(Expr_node* t); // 子类通过覆写finalize方法,实现对资源清理行为的定制
};
// 表达式(子树的概念),其中,init*方法族提供了构建子树的高层API,方便用户使用
CLASS(Expr)
{
int use; // 引用计数
Expr_node* p; // 子树的根节点
// 构建整数表达式(包含一个整数值,无子表达式)
void (*initInt)(Expr* t, int);
// 构建一元表达式(包含一个操作符,一个子表达式)
void (*initUnary)(Expr* t, const char*, Expr*);
// 构建一元表达式的重载形式(通过传入个整型值参数,构造一个子表达式为整数表达式的一元表达式)
void (*initUnaryX)(Expr* t, const char*, int);
// 构建二元表达式(包含一个操作符,二个子表达式)
void (*initBinary)(Expr* t, const char*, Expr*, Expr*);
// 构建二元表达式的重载形式(通过传入个整型值参数,构造两个子表达式均为整数表达式的二元表达式)
void (*initBinaryX)(Expr* t, const char*, int, int);
void (*print)(Expr* t); // 打印子树
};
// 整数表达式节点
CLASS(Int_node)
{
EXTENDS(Expr_node); // 继承Expr_node
int n; // 整数值
// 初始化整数表达式节点(传入整数值)
void (*init)(Int_node* t, int k);
};
// 一元表达式节点
CLASS(Unary_node)
{
EXTENDS(Expr_node); // 继承Expr_node
char op[3]; // 假设操作符最长不超过2个字符
Expr* opnd; // 子表达式
// 初始化一元表达式节点(传入一个操作符和一个子表达式)
void (*init)(Unary_node* t, const char* a, Expr* b);
};
// 二元表达式节点
CLASS(Binary_node)
{
EXTENDS(Expr_node); // 继承Expr_node
char op[3]; // 假设操作符最长不超过2个字符
Expr* left; // 左子表达式
Expr* right; // 右子表达式
// 初始化二元表达式节点(传入一个操作符和两个子表达式)
void (*init)(Binary_node* t, const char* a, Expr* b, Expr* c);
};
#endif
//expr.c
…… // 包含所需头文件
ABS_CTOR(Expr_node)
cthis->use = 1; // 构造函数中,将引用计数初始化为
END_ABS_CTOR
// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)
DTOR(Expr_node)
if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己
{
cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)
return lw_oopc_true; // 返回true,表示析构成功,可以释放内存
}
return lw_oopc_false; // 返回false,表示析构失败,不能释放内存
END_DTOR
// 构建整数表达式(包含一个整数值,无子表达式),n为整数值
void Expr_initInt(Expr* expr, int n)
{
Int_node* intNode = Int_node_new(lw_oopc_file_line);
intNode->init(intNode, n);
expr->p = SUPER_PTR(intNode, Expr_node);
}
…… // 因篇幅所限,构建一元表达式、二元表达式以及对应的重载形式的函数实现代码省略
// 打印表达式(子树)
void Expr_print(Expr* t)
{
Expr_node* p = t->p;
p->print(p);
}
CTOR(Expr)
FUNCTION_SETTING(initInt, Expr_initInt);
FUNCTION_SETTING(initUnary, Expr_initUnary);
FUNCTION_SETTING(initUnaryX, Expr_initUnaryX);
FUNCTION_SETTING(initBinary, Expr_initBinary);
FUNCTION_SETTING(initBinaryX, Expr_initBinaryX);
FUNCTION_SETTING(print, Expr_print);
cthis->use = 1; // 构造函数中,将引用计数初始化为
END_CTOR
// Expr的析构函数(DTOR/END_DTOR用于实现析构函数语义)
DTOR(Expr)
if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己
{
Expr_node_delete(cthis->p);
return lw_oopc_true;
}
return lw_oopc_false;
END_DTOR
// 整数表达式节点的初始化
void Int_node_init(Int_node* t, int k)
{
t->n = k;
}
// 整数表达式节点的打印
void Int_node_print(Expr_node* t)
{
Int_node* cthis = SUB_PTR(t, Expr_node, Int_node);
printf("%d", cthis->n);
}
// 整数表达式节点的资源清理
void Int_node_finalize(Expr_node* t)
{
// 什么都不需要做
}
CTOR(Int_node)
SUPER_CTOR(Expr_node);
FUNCTION_SETTING(init, Int_node_init);
FUNCTION_SETTING(Expr_node.print, Int_node_print);
FUNCTION_SETTING(Expr_node.finalize, Int_node_finalize);
END_CTOR
…… // 因篇幅所限,一(二)元表达式节点的初始化、打印、资源清理、构造等函数的实现代码省略
//main.c
#include "stdio.h"
#include "Expr.h"
int main()
{
Expr* expr = Expr_new();
…… // 创建expr1、expr2、expr3的代码
expr1->initUnaryX(expr1, "-", 5);
expr2->initBinaryX(expr2, "+", 3, 4);
expr3->initBinary(expr3, "*", expr1, expr2);
expr->initBinary(expr, "*", expr3, expr3);
expr3->print(expr3);
printf("/n");
expr->print(expr);
printf("/n");
Expr_delete(expr);
…… // 删除expr3、expr2、expr1的代码
return 0;
}
程序运行的效果如下:
怎么样?效果还不错吧,最重要的是,我们的C语言代码现在已经完全是面向对象的。
方案的可扩展性如何?
假设我们希望添加一种Ternary_node类型来表示三元操作符,如?:(也就是if-then-else操作符),看看,难度有多大?
事实上,正是因为前面的设计是面向对象的,要增加一种节点类型易如反掌:
// 三元表达式节点
CLASS(Ternary_node)
{
EXTENDS(Expr_node);
char op[3]; // 假设操作符最长不超过2个字符
Expr* left;
Expr* middle;
Expr* right;
// 初始化三元表达式节点(传入一个操作符和三个子表达式)
void (*init)(Ternary_node* t, const char* op, Expr* left, Expr* middle, Expr* right);
};
在Expr中添加创建三元表达式的方法:
// 表达式(子树的概念),其中,init*方法族提供了构建子树的高层API,方便用户使用
CLASS(Expr)
{
int use; // 引用计数
Expr_node* p; // 子树的根节点
…… // 既有实现
// 构建三元表达式(包含一个操作符,三个子表达式)
void (*initTernary)(Expr* t, const char*, Expr*, Expr*, Expr*);
// 构建三元表达式的重载形式(通过传入一个整型值参数,构造三个子表达式均为整数表达式的三元表达式)
void (*initTernaryX)(Expr* t, const char*, int, int, int);
…… // 既有实现
};
请读者参照Binary_node的现有实现,实现出Ternary_node,这里不再赘述。一旦实现出Ternary_node,我们就可以这样创建表达式树并打印:
…… // 创建expr1、expr2、expr3、expr对象(指针)
expr1->initUnaryX(expr1, "-", 0);
expr2->initUnaryX(expr2, "-", 5);
expr3->initBinaryX(expr3, "+", 3, 4);
expr->initTernary(expr, "?:", expr1, expr2, expr3);
expr->print(expr);
printf("/n");
为了支持新的节点类型,对原有代码的更动很少(仅对Expr类有增加方法),而且只有新增操作(新增类,新增方法),但没有修改操作(指修改原有方法),面向对象的设计赋予了系统极大的弹性,让程序在应对变化时,更加从容。在这个例子中,LW_OOPC帮助我们在C语言的世界里营造出OO的天地,带领我们再一次领略了面向对象的风采。
LW_OOPC最佳实践
说得简单一点,要想使用好LW_OOPC这套宏,还得首先懂面向对象,要遵循面向对象设计的那些大原则,比如开闭原则等。在C语言中使用面向对象,根据实际使用的情况,给出如下建议:
1) 继承层次不宜过深,建议最多三层(接口、抽象类、具体类,参见 图 1 和 图 2 )
继承层次过深,在Java/C#/C++中均不推崇,在C语言中实践面向对象的时候,尤其要遵循这一点,只有这样,代码才能简单清爽。
2) 尽量避免多重继承
尽可能使用单线继承,但可实现多个接口(与Java中的单根继承类似)。
3) 尽量避免具体类继承具体类
具体类继承具体类,不符合抽象的原则,要尽量避免。
4) 各继承层次分别维护好自己的数据
子类尽量不要直接访问祖先类的数据,如果确实需要访问,应当通过祖先类提供的函数,以函数调用的方式间接访问。
图 1
图 2
LW_OOPC的优点:
1) 轻量级
2) 广泛的适应性,能够适应各种平台,各种编译器(能支持C的地方,基本上都能支持)
3) 帮助懂OO的Java/C++程序员写出面向对象的C程序。
4) 使用C,也能引入OO的设计思想和方法,在团队的C/C++分歧严重时可能非常有用。
LW_OOPC的缺点:
1) 无法支持重载(C语言不支持所致)
2) 不完全的封装(无法区分私有、保护和公有)
LW_OOPC的INTERFACE/ABS_CLASS/CLASS三个宏展开后都是C语言的struct,其成员全是公有的,宏本身并无能力提供良好地封装层次的支持,所以,只能从编程规范和编程风格上进行引导。
3) 不支持RTTI
既然不支持RTTI,那么显然也无法支持安全的向下转型(C++中的dynamic_cast的转型功能)
4) 不支持拷贝构造以及赋值语义
5) 转换成接口的表述有点麻烦,表达形式相比C++要啰嗦很多。
6) 有学习成本,需要用户学习并习惯这套宏
前四条缺点,实质上并非是LW_OOPC的缺点,而是C相对C++而言的缺点,在这里,之所以也一并列上,是希望用户不要对LW_OOPC抱太高的期望,毕竟它也只是一套C语言的宏而已,C语言有的缺点,LW_OOPC并不能够解决。
总结:
尽管如此,在使用C语言编程的时候,在某些情形下,你可能想要通过面向对象来更好的组织代码。偶尔,你也想要用用某个设计模式,此时,这套宏能够帮得上忙,使用它,有助于写出相对易于理解和维护的面向对象的代码。因篇幅所限,本文中没有介绍LW_OOPC的高级特性,譬如对内存泄漏检测的支持。示例的完整代码、LW_OOPC的最新版本以及关于这套宏的更加详细的使用指南,请读者访问http://lwoopc.sourceforge.net获取。最后,期望有兴趣的读者,发挥聪明才智,提出改进建议,让LW_OOPC变得越来越好!
幕后花絮:
在完善LW_OOPC宏的过程中,我也认真研究了参考资料中列出的材料。最初V1.0版本有将近25个宏,后来,收到一些同事的反馈,认为少量宏晦涩难记,而且不易理解,经过认真考虑,删除掉5个宏,形成现在的20个宏。相比其他用C实现面向对象的方案,LW_OOPC简洁优雅,每个宏的命名都经过仔细地推敲,尽可能做到简单易懂,便于记忆。
但愿LW_OOPC真的能够帮助到奋斗在一线的C程序员们。
参考资料:
[1] 高焕堂。UML+OOPC嵌入式C语言开发精讲,电子工业出版社,2008 年9月。
[2] Object-oriented Programming with ANSI-C,下载地址:http://www.planetpdf.com/codecuts/pdfs/ooc.pdf。
[3] C实现面向对象的方法,http://www.eventhelix.com/RealtimeMantra/basics/object_oriented_programming_in_c.htm
LW_OOPC宏配置及使用指南
金永华
2010/2/15
LW_OOPC是一套轻量级的面向对象C语言编程框架。它是一套C语言的宏,总共1个.h文件(如果需要内存泄漏和调试打印支持,需要增加1个.c文件(lw_oopc.c,约145行)),20个宏,约130行代码,非常的轻量级,但却很好的支持了很多面向对象的特性,比如继承、多态,可以优美的实现面向接口编程。
注意,这里特别强调一下,使用LW_OOPC的前提是:在C语言下。如果您所在的团队已经在使用C++,那么LW_OOPC对于这种情形是没有价值的。也就是说,LW_OOPC希望能够帮助到那些懂OO的程序员,即便是在用C语言编程,依然能够编写出面向对象的程序。
言归正传,本文将对LW_OOPC的配置和使用方法进行讲解,并对这些宏逐个进行细致讲解。期望本文能给希望在实践中应用LW_OOPC的C程序员带来帮助。
LW_OOPC当前版本共有两个文件:lw_oopc.h和lw_oopc.c。LW_OOPC的使用非常简单,只需要将这两个文件加入工程即可。常规情况下,建议用户同时使用上述两个文件,因为借助lw_oopc.c,我们可以监测到内存泄漏,通过打开调试开关,我们能够观察内存分配和释放的调试打印信息,这将有助于我们除错,减少调试的时间。如果你不需要监测内存泄漏(譬如准备发布程序),此时,你并不需要lw_oopc.c,而只需要lw_oopc.h即可。
LW_OOPC配置
在lw_oopc.h中,有这么几行代码:
// 配置宏(两种配置选其一):
// LW_OOPC_USE_STDDEF_OFFSETOF 表示使用C标准定义的offsetof
// LW_OOPC_USE_USER_DEFINED_OFFSETOF 表示使用用户自定义的lw_oopc_offsetof宏
#define LW_OOPC_USE_STDDEF_OFFSETOF
//#define LW_OOPC_USE_USER_DEFINED_OFFSETOF
// 是否支持内存泄露检测,缺省不支持
//#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
从上边的注释,我们可以看出,LW_OOPC需要使用offsetof宏,如果你的开发环境能够支持C标准定义的offsetof宏,那么什么都不需要动。如果你的开发环境不能支持C标准定义的offsetof宏,那么可以选择使用用户自定义的lw_oopc_offsetof宏(如果你的开发环境连用户自定义的offsetof宏都不支持,在这种情形下,LW_OOPC将无法很好的支持多态特性,很遗憾,你只能与LW_OOPC失之交臂)。
关于LW_OOPC对内存泄露检测以及调试打印的支持,我们将在LW_OOPC高级配置部分进行详细讲解。
LW_OOPC宏说明
1) INTERFACE
INTERFACE用于声明接口,譬如:
INTERFACE(IMoveable)
{
void (*move)(IMoveable* t); // Move行为
};
在LW_OOPC中,声明接口、抽象类和具体类的方法成员比较特殊,均是函数指针类型的成员。事实上,LW_OOPC正是借助了函数指针的特性,完成了多态功能的模拟。
2) ABS_CLASS
ABS_CLASS用于声明抽象类,譬如:
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 (*eat)(Animal* t); // 动物都会吃(抽象方法,由子类实现)
void (*breathe)(Animal* t); // 动物都会呼吸(抽象方法,由子类实现)
void (*init)(Animal* t, const char* name, int age); // 初始化昵称和年龄
};
3) CLASS
CLASS用于声明具体类,譬如:
CLASS(Fish)
{
EXTENDS(Animal); // 继承Animal抽象类
IMPLEMENTS(IMoveable); // 实现IMoveable接口
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
};
在该例中,我们声明了Fish类,并让该类继承Animal抽象类,并且实现IMoveable接口。
4) EXTENDS 和 IMPLEMENTS
在介绍CLASS宏的时候,我们在代码中看到有两个宏:EXTENDS和IMPLEMENTS,如果你查看lw_oopc.h的源码,你将会发现他们是一模一样的:
#define IMPLEMENTS(type) struct type type
#define EXTENDS(type) struct type type
之所以同时提供继承和实现关键字,仅仅是为了让熟悉Java的人更加容易理解LW_OOPC宏。(注意,在LW_OOPC中,建议将继承和实现声明写在结构体的开头,把继承和实现声明摆在显眼的位置,有助于阅读代码的人更好的理解代码)
5) ABS_CTOR 和 END_ABS_CTOR
ABS_CTOR和END_ABS_CTOR用于定义抽象类的构造函数,例如:
/* 设置动物的昵称*/
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
前面,我们声明Animal是一个抽象类,对应的构造函数定义需要使用ABS_CTOR和END_ABS_CTOR。ABS_CTOR是Abstract Constructor的缩写。
6) FUNCTION_SETTING
在介绍ABS_CTOR和END_ABS_CTOR宏的时候,我们在代码中又发现一个陌生的宏:
FUNCTION_SETTING,这个宏在LW_OOPC中的地位非同凡响,没有它,LW_OOPC就不可能存在。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct)分配内存,然后,最重要的一个步骤是为结构体中的函数指针成员赋值,这一过程,也可以称为函数绑定(有点类似C++中的动态联编)。函数绑定的过程由FUNCTION_SETTING宏来完成。
我们来看看FUNCTION_SETTING宏是如何实现的:
#define FUNCTION_SETTING(f1, f2) cthis->f1 = f2;
看到这里,想必读者应该会心一笑了。:)
7) CTOR 和 END_CTOR
CTOR和END_CTOR用于定义具体类的构造函数,例如:
/* 鱼的吃行为 */
void Fish_eat(Animal* t)
{
printf("鱼吃水草!/n");
}
/* 鱼的呼吸行为 */
void Fish_breathe(Animal* t)
{
printf("鱼用鳃呼吸!/n");
}
/* 鱼的移动行为 */
void Fish_move(IMoveable* t)
{
printf("鱼在水里游!/n");
}
/* 初始化鱼的昵称和年龄 */
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(Animal.eat, Fish_eat);
FUNCTION_SETTING(Animal.breathe, Fish_breathe);
FUNCTION_SETTING(IMoveable.move, Fish_move);
FUNCTION_SETTING(init, Fish_init);
END_CTOR
从代码上看,CTOR/END_CTOR 与 ABS_CTOR/END_ABS_CTOR 的使用方式完全相同,的确是,不过,背后,这两对宏的实现方式略有差异,建议有兴趣的读者,认真研究一下LW_OOPC的源码。这里,简单地说明如下:我们希望明确区分抽象类和具体类的概念,抽象类是不可以创建对象的,而具体类则可以。前面,我们声明了Animal是抽象类,Fish类是具体类,那么,我们希望:
Animal* animal = Animal_new(); // 不允许这样写!
Fish* fish = Fish_new(); // 允许这样写!
8) SUPER_CTOR
在讲解CTOR/END_CTOR宏的时候,又出现一个陌生的宏:SUPER_CTOR。它的功能与Java中的super关键字非常类似。
SUPER_CTOR(Animal);
意为:调用Animal类的构造函数。(建议将SUPER_CTOR写在“构造函数”体的开头)
9) DTOR 和 END_DTOR
DTOR和END_DTOR用于定义“析构函数”,例如:
// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)
DTOR(Expr_node)
if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己
{
cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)
lw_oopc_free(cthis);
}
END_DTOR
这里,特别说明一点,为了模拟C++中的this指针,我们允许用户在ABS_CTOR/END_ABS_CTOR、CTOR/END_CTOR、DTOR/END_DTOR定义块中可以直接使用cthis。
10) SUPER_PTR
SUPER_PTR用于“向上转型”,将对象指针向上转型成直接父类或者直接接口:
Fish* fish = Fish_new(); // 创建鱼对象
// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
fish->init(fish, "小鲤鱼", 1);
// 将fish指针转型为Animal类型指针,并赋值给animals数组的第一个成员
Animal* animal = SUPER_PTR(fish, Animal);
// 将fish指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第一个成员
IMoveable* moveFish = SUPER_PTR(fish, IMoveable);
这里,直接父类很容易理解,直接接口,呵呵,暂且认为我是首创的吧。J
我们再来看一下Fish类的声明代码:
CLASS(Fish)
{
EXTENDS(Animal); // 继承Animal抽象类
IMPLEMENTS(IMoveable); // 实现IMoveable接口
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
};
看:IMPLEMENTS(IMoveable); // 实现IMoveable接口
对Fish类来讲,IMoveable就是它的直接接口。
11) SUPER_PTR_2 和 SUPER_PTR_3
SUPER_PTR_2和SUPER_PTR_3是SUPER_PTR的高级版本,它们的作用与SUPER_PTR是完全类似的,都是向上转型,只不过,SUPER_PTR_2是向上转两次,SUPER_PTR_3是向上转三次。也就是说,SUPER_PTR_2用于将自身的指针转型为爷爷辈指针,SUPER_PTR_3用于将自身的指针转型为曾祖辈指针。看看SUPER_PTR_2和SUPER_PTR_3的代码:
#define SUPER_PTR_2(cthis, father, grandfather) /
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
#define SUPER_PTR_3(cthis, father, grandfather, greatgrandfather) /
SUPER_PTR(SUPER_PTR_2(cthis, father, grandfather), greatgrandfather)
看到了吧,SUPER_PTR_2其实是两次SUPER_PTR的叠加。SUPER_PTR_3是三次SUPER_PTR的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。
12) SUB_PTR
SUB_PTR用于“向下转型”,将父类指针向下转型成子类:
/* 鱼的吃行为*/
void Fish_eat(Animal* t)
{
Fish* fish = SUB_PTR(t, Animal, Fish);
…… // 这里可以访问Fish类的成员
printf("鱼吃水草!/n");
}
eat方法是Animal的一个方法,Fish类覆写了该方法,注意,由于该方法的第一个参数类型是:Animal*,在实现Fish类的eat方法Fish_eat时,如果想要访问到Fish类的成员,需要将第一个参数向下转型成Fish*,这就是SUB_PTR所完成的事情。
13) SUB_PTR_2 和 SUB_PTR_3
SUB_PTR_2和SUB_PTR_3是SUB_PTR的高级版本,它们的作用与SUB_PTR是完全类似的,都是向下转型,只不过,SUB_PTR_2是向下转两次,SUB_PTR_3是向下转三次。也就是说,SUB_PTR_2用于将自身的指针转型为孙子辈指针,SUB_PTR_3用于将自身的指针转型为曾孙辈指针。看看SUB_PTR_2和SUB_PTR_3的代码:
#define SUB_PTR_2(selfptr, self, child, grandchild) /
SUB_PTR(SUB_PTR(selfptr, self, child), child, grandchild)
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) /
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
看到了吧,SUB_PTR_2其实是两次SUB_PTR的叠加。SUB_PTR_3是三次SUB_PTR的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。
14) INHERIT_FROM
INHERIT_FROM用于访问直接父类的成员,例如:
Dog* dog = Dog_new(); // 创建狗对象
// 初始化狗对象的昵称为:牧羊犬,年龄为:2岁
dog->init(dog, "牧羊犬", 2);
INHERIT_FROM(Animal, dog, age) = 3; // 把牧羊犬的年龄修改为3岁
printf("狗的年龄是:%d岁!/n", INHERIT_FROM(Animal, dog, age)); // 打印狗的年龄
注意,LW_OOPC的上一个版本,我们同时提供了INHERIT_FROM_2和INHERIT_FROM_3这两个宏,INHERIT_FROM_2用于访问爷爷辈的成员,INHERIT_FROM_3用于访问曾祖辈的成员。我们认为应当尽量避免使用INHERIT_FROM_2和INHERIT_FROM_3宏,因为,这会导致类的继承关系中存在严重的数据耦合(自身类可以直接访问爷爷辈,甚至曾祖父辈的成员),这将导致程序难于理解,难于维护。因此,在当前版本中,删除了INHERIT_FROM_2和INHERIT_FROM_3宏,仅仅保留INHERIT_FROM。一般情况下,我们可以通过更加合理的函数封装,让当前类通过祖先类提供的方法间接地访问祖先类的成员。如果确实要在当前类中直接访问爷爷辈甚至曾祖辈的成员,我们可以先通过SUPER_PTR_2和SUPER_PTR_3将当前对象的指针转型为对应的祖先类指针,然后再通过其指针访问其成员。
写到这里,LW_OOPC所有的宏都介绍完毕了。下面,我们介绍LW_OOPC对内存泄漏和调试信息打印的支持。
LW_OOPC高级配置
缺省情况下,LW_OOPC不支持内存泄漏检测,如果需要支持,只要将
//#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR去掉行注释符:
#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR即可。
一旦你决定让LW_OOPC支持内存泄露检测,那么,你必须同时将lw_oopc.c加入工程。我们再来看看lw_oopc.c中的配置:
// 是否支持调试信息打印(内存分配和释放的详细信息),缺省关闭打印
//#define LW_OOPC_PRINT_DEBUG_INFO
缺省情况下,LW_OOPC不支持调试信息打印,如果需要支持,只要将
//#define LW_OOPC_PRINT_DEBUG_INFO去掉行注释符:
#define LW_OOPC_PRINT_DEBUG_INFO即可。
看一个实例,实例工程是:ExprAdvance,可从http://lwoopc.sourceforge.net上下载。
第一步:
用VC6/VC2005/VC2008打开对应的工程文件,打开lw_oopc.h,将LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR的注释打开,如下所示:
// 是否支持内存泄露检测,缺省不支持
#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
main.c的内容,一开始是这样的:
#include "stdio.h"
#include "Expr.h"
int main()
{
Expr* expr1 = Expr_new();
Expr* expr2 = Expr_new();
Expr* expr3 = Expr_new();
Expr* expr = Expr_new();
expr1->initUnaryX(expr1, "-", 0);
expr2->initUnaryX(expr2, "-", 5);
expr3->initBinaryX(expr3, "+", 3, 4);
expr->initTernary(expr, "?:", expr1, expr2, expr3);
expr->print(expr);
printf("/n");
Expr_delete(expr);
Expr_delete(expr3);
Expr_delete(expr2);
Expr_delete(expr1);
return 0;
}
打开内存泄漏检测的配置项后,重新编译工程,我们发现了一些编译错误:
1>c:/projects/expradvance/main.c(6) : error C2198: “Expr_new”: 用于调用的参数太少
1>c:/projects/expradvance/main.c(7) : error C2198: “Expr_new”: 用于调用的参数太少
1>c:/projects/expradvance/main.c(8) : error C2198: “Expr_new”: 用于调用的参数太少
1>c:/projects/expradvance/main.c(9) : error C2198: “Expr_new”: 用于调用的参数太少
为了支持内存泄漏的检测,我们需要给每个“构造函数”传入文件名和行号参数,LW_OOPC已经为我们准备了一个宏:lw_oopc_file_line,这里,这个宏违反了编程规范(本来宏应该是全部采用大写字母),不过,这里之所以采用小写,是故意希望用户产生一种错觉,让用户以为lw_oopc_file_line是一个特殊的实参,该实参包含了文件和行号的信息。看看修正编译错误后的代码:
#include "stdio.h"
#include "Expr.h"
int main()
{
Expr* expr1 = Expr_new(lw_oopc_file_line);
Expr* expr2 = Expr_new(lw_oopc_file_line);
Expr* expr3 = Expr_new(lw_oopc_file_line);
Expr* expr = Expr_new(lw_oopc_file_line);
expr1->initUnaryX(expr1, "-", 0);
expr2->initUnaryX(expr2, "-", 5);
expr3->initBinaryX(expr3, "+", 3, 4);
expr->initTernary(expr, "?:", expr1, expr2, expr3);
expr->print(expr);
printf("/n");
Expr_delete(expr);
Expr_delete(expr3);
Expr_delete(expr2);
Expr_delete(expr1);
lw_oopc_report();
return 0;
}
看看
Expr* expr1 = Expr_new(lw_oopc_file_line);
相比Expr* expr1 = Expr_new();
多了一个“实参”:lw_oopc_file_line。
仅仅增加了一个参数,我们并不能检测到该程序是否有内存泄漏。我们还得在main函数的最后一条语句:return 0;之前加一条语句:
lw_oopc_report();
现在,我们再一次编译并运行程序,结果如下:
看到lw_oopc: no memory leak.这句话了吧。这表明我们的程序没有内存泄漏。另外,LW_OOPC还会生成memory_detector_result.txt文件,如下图所示:
我们试着注释掉一句代码,如下所示:
//Expr_delete(expr1);
在编译和运行一下,我们看到我们的程序有内存泄漏:
前面的例子,如果都是假设想要在堆上创建对象,有一种情形,我们一直没有涉及:如果我们想让代码在堆栈上创建对象,代码该如何写?
很简单,创建对象:
Expr expr;
Expr_ctor(&expr);
销毁对象:
Expr_dtor(&expr);
到这里,本文该结束了,希望本文已经将LW_OOPC的配置以及所有的宏讲明白了。:)如果你看了这篇文章,还有疑虑,可以查看lw_oopc.h和lw_oopc.c源文件。源码面前,没有秘密。:)如果你看了源码,还有疑问,可以与我联系。
我的邮箱:
金永华
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
// Released under the terms of the GNU Library or Lesser General Public License (LGPL).
// Author: Tom Kao(中文名:高焕堂),MISOO团队,Yonghua Jin(中文名:金永华)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef LW_OOPC_H_INCLUDED_
#define LW_OOPC_H_INCLUDED_
// 配置宏(两种配置选其一):
// LW_OOPC_USE_STDDEF_OFFSETOF 表示使用C标准定义的offsetof
// LW_OOPC_USE_USER_DEFINED_OFFSETOF 表示使用用户自定义的lw_oopc_offsetof宏
#define LW_OOPC_USE_STDDEF_OFFSETOF
//#define LW_OOPC_USE_USER_DEFINED_OFFSETOF
// 是否支持内存泄露检测,缺省不支持
//#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
#include "malloc.h"
typedef int lw_oopc_bool;
#define lw_oopc_true 1
#define lw_oopc_false 0
#ifdef LW_OOPC_USE_STDDEF_OFFSETOF
#include
#define LW_OOPC_OFFSETOF offsetof
#endif
#ifdef LW_OOPC_USE_USER_DEFINED_OFFSETOF
// 有些环境可能不支持,不过,这种情形极少出现
#define LW_OOPC_OFFSETOF(s,m) (size_t)&(((s*)0)->m)
#endif
#ifdef LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
void* lw_oopc_malloc(size_t size, const char* type, const char* file, int line);
void lw_oopc_free(void* memblock);
void lw_oopc_report();
#define lw_oopc_file_line __FILE__, __LINE__
#define lw_oopc_file_line_params const char* file, int line
#define lw_oopc_delete lw_oopc_free
#else
void lw_oopc_report();
#define lw_oopc_file_line
#define lw_oopc_file_line_params
#define lw_oopc_free free
#define lw_oopc_delete lw_oopc_free
#endif
#define INTERFACE(type) /
typedef struct type type; /
void type##_ctor(type* t); /
int type##_dtor(type* t); /
struct type
#define ABS_CLASS(type) /
typedef struct type type; /
void type##_ctor(type* t); /
int type##_dtor(type* t); /
void type##_delete(type* t); /
struct type
#define CLASS(type) /
typedef struct type type; /
type* type##_new(lw_oopc_file_line_params); /
void type##_ctor(type* t); /
int type##_dtor(type* t); /
void type##_delete(type* t); /
struct type
#ifdef LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
#define CTOR(type) /
type* type##_new(const char* file, int line) { /
struct type *cthis; /
cthis = (struct type*)lw_oopc_malloc(sizeof(struct type), #type, file, line); /
if(!cthis) /
{ /
return 0; /
} /
type##_ctor(cthis); /
return cthis; /
} /
/
void type##_ctor(type* cthis) {
#else
#define CTOR(type) /
type* type##_new() { /
struct type *cthis; /
cthis = (struct type*)malloc(sizeof(struct type)); /
if(!cthis) /
{ /
return 0; /
} /
type##_ctor(cthis); /
return cthis; /
} /
/
void type##_ctor(type* cthis) {
#endif
#define END_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 }
#define ABS_CTOR(type) /
void type##_ctor(type* cthis) {
#define END_ABS_CTOR }
#define FUNCTION_SETTING(f1, f2) cthis->f1 = f2;
#define IMPLEMENTS(type) struct type type
#define EXTENDS(type) struct type type
#define SUPER_PTR(cthis, father) ((father*)(&(cthis->##father)))
#define SUPER_PTR_2(cthis, father, grandfather) /
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
#define SUPER_PTR_3(cthis, father, grandfather, greatgrandfather) /
SUPER_PTR(SUPER_PTR_2(cthis, father, grandfather), greatgrandfather)
#define SUPER_CTOR(father) /
father##_ctor(SUPER_PTR(cthis, father));
#define SUB_PTR(selfptr, self, child) /
((child*)((char*)selfptr - LW_OOPC_OFFSETOF(child, self)))
#define SUB_PTR_2(selfptr, self, child, grandchild) /
SUB_PTR(SUB_PTR(selfptr, self, child), child, grandchild)
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) /
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
#define INHERIT_FROM(father, cthis, field) cthis->father.field
#endif
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
// Released under the terms of the GNU Library or Lesser General Public License (LGPL).
// Author: Tom Kao(中文名:高焕堂),MISOO团队,Yonghua Jin(中文名:金永华)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "lw_oopc.h"
// 是否支持调试信息打印(内存分配和释放的详细信息),缺省关闭打印
//#define LW_OOPC_PRINT_DEBUG_INFO
#ifdef LW_OOPC_PRINT_DEBUG_INFO
#define lw_oopc_dbginfo printf
#else
#define lw_oopc_dbginfo
#endif
#define LW_OOPC_MAX_PATH 260
#define LW_OOPC_MEMORY_DETECTOR_RST "memory_detector_result.txt"
typedef struct LW_OOPC_MemAllocUnit
{
char file[LW_OOPC_MAX_PATH]; // 文件名
int line; // 行号
void* addr; // 内存地址
size_t size; // 内存块大小
struct LW_OOPC_MemAllocUnit* next; // 下一个内存块
} LW_OOPC_MemAllocUnit;
#ifdef LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
static LW_OOPC_MemAllocUnit* lw_oopc_memAllocList = 0;
void* lw_oopc_malloc(size_t size, const char* type, const char* file, int line)
{
void* addr = malloc(size);
if (addr != 0)
{
LW_OOPC_MemAllocUnit* pMemAllocUnit = malloc(sizeof(LW_OOPC_MemAllocUnit));
if (!pMemAllocUnit)
{
fprintf(stderr, "lw_oopc: error! malloc alloc unit failed./n");
exit(1);
}
if (strlen(file) >= LW_OOPC_MAX_PATH)
{
fprintf(stderr, "lw_oopc: error! file name is more than %d character: %s/n", LW_OOPC_MAX_PATH, file);
exit(1);
}
strcpy(pMemAllocUnit->file, file);
pMemAllocUnit->line = line;
pMemAllocUnit->addr = addr;
pMemAllocUnit->size = size;
pMemAllocUnit->next = lw_oopc_memAllocList;
lw_oopc_memAllocList = pMemAllocUnit;
lw_oopc_dbginfo("lw_oopc: alloc memory in %p, size: %d, object type: %s, file: %s, line: %d/n", addr, size, type, file, line);
}
return addr;
}
void lw_oopc_free(void* memblock)
{
LW_OOPC_MemAllocUnit* prevUnit = 0;
LW_OOPC_MemAllocUnit* currUnit = lw_oopc_memAllocList;
while(currUnit != 0)
{
if (currUnit->addr == memblock)
{
lw_oopc_dbginfo("lw_oopc: free memory in %p, size: %d/n", currUnit->addr, currUnit->size);
if (prevUnit == 0)
{
lw_oopc_memAllocList = currUnit->next;
free(currUnit->addr);
return;
}
prevUnit->next = currUnit->next;
return;
}
else
{
prevUnit = currUnit;
currUnit = currUnit->next;
}
}
if (currUnit == 0)
{
fprintf(stderr, "lw_oopc: error! you attemp to free invalid memory./n");
exit(1);
}
}
void lw_oopc_report()
{
LW_OOPC_MemAllocUnit* currUnit = lw_oopc_memAllocList;
FILE* fp = fopen(LW_OOPC_MEMORY_DETECTOR_RST, "w+");
if(!fp)
{
fprintf(stderr, "lw_oopc: error occured, open file: %s failed./n", LW_OOPC_MEMORY_DETECTOR_RST);
}
if (currUnit != 0)
{
fprintf(stderr, "lw_oopc: memory leak:/n");
if(fp)
{
fprintf(fp, "lw_oopc: memory leak:/n");
}
while(currUnit != 0)
{
fprintf(stderr, "memory leak in: %p, size: %d, file: %s, line: %d/n", currUnit->addr, currUnit->size, currUnit->file, currUnit->line);
if(fp)
{
fprintf(fp, "memory leak in: %p, size: %d, file: %s, line: %d/n", currUnit->addr, currUnit->size, currUnit->file, currUnit->line);
}
currUnit = currUnit->next;
}
}
else
{
printf("lw_oopc: no memory leak./n");
if(fp)
{
fprintf(fp, "lw_oopc: no memory leak./n");
}
}
if(fp)
{
fclose(fp);
}
}
#else
void lw_oopc_report()
{
}
#endif