OpenVINOTM,给你看得见的未来!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 用C语言进行面向对象编程(老站转)

共1条 1/1 1 跳转至

用C语言进行面向对象编程(老站转)

菜鸟
2002-05-28 19:58:00    评分
早年使用SingleStep 6.5对Mc68332开发程序, 没有激活编译器C++特性的密码(虽然现在看来这密码如同儿戏), 但又想使用OO编程, 就找了些资料, 希望在C语言中按照一定规范进行OO编程, 实现封装性, 抽象性; 清华影印版"OOSC"写得不错, 其中章节讲拉OO In C. 我后面程序确实照这个想法重写了一次, 觉得还行; 后来, 编译器支持C++拉, 就不用拉, 不过思想不错. 对于一些使用C编译器的, 如C51等, 以及其它过程语言的, 都可以利用这种思想实现OO编程, 对程序质量有帮助. 下面的这篇文章原文来自ESP, 我在学习时翻译了部分, 不全 希望对大家有所帮助! amine 2002-01-04 Edited by - amine on 2002-01-05 11:03:09 -------------------------------------------------------------------------------- 编辑 发表於:2002-01-05 - 10:59:00 IP: 211.97.*.* amine 版主 来自: 发表总数:519 查看   短消息   电子邮件 -------------------------------------------------------------------------------- ******************************************************************************* C中可移植的继承性和多态性 Fr: [ESP-9712] By: Miro Samek Rd: Amine Chen[a_mine@263.net] ******************************************************************************* 虽然面向对象设计几乎与语言无关,但大部分文献都采用[assume]C++,Smalltalk,Java 用于OO实现.本文从更低层次看,认为过程语言(如C)同样可用于OO实现,想应用OO的嵌入开发 人员不必转换到OO语言. *是否可以用非OO语言(如C)编写OO程序? *在没有C++编译器的小嵌入系统中,如何实现OO设计? *如何改善C编码风格,以使代码可更好复用,更模块化[modular],更健壮? *继承和多态实际是怎样工作的? *使用C而不用OO语言实现OO设计,必须损失[compromise]多少便利和表现? 为了回答这些问题,本文提出了一个小型,可移植,高效的OO概念的C语言实现. 这些OO概念如下(本文解释如下这些"设计模式"在C中的实现): *封装性--包装数据和函数为类,以及信息隐藏和模块化的技巧; *继承性--基于已有类定义新类和行为[behavior]的能力,以获得代码重用&组织; *多态性--同一消息送到不同对象,导致不同行为; 在实现中,采用Java语言继承和多态的方法. 类继承(或实现继承)作为单继承模型提供, Object抽象类处于类层次的根部. 相反, 该实现允许多实现继承(提供多接口继承(类型继承)), 允许类实现多个Java风格的接口. *支持[leveraging]OO技术; *从过程到OO思考的平滑过渡; 1.封装性: ------------------------------------------------------------------------------- 通过使每个类属性(instance variable)为C结构的一个域,在C中你可以包装数据和函数. 实现类方法为C函数,其第一个参数为指向属性结构的指针(this指针).通过对方法名采用一致 的命名规则,可进一步加强属性和方法之间的联合.最常用的规则为合并结构名(类名)和操作 名.函数名的改变是名字修饰的一部分(也称作名字损坏[mangling]),大多数C++编译器隐含 执行函数名的改变.因为名称修饰消除了不同类之间的方法名冲突, 所以它有效地将平面的 [flat]C函数名字空间划分为独立的,嵌套在类中的名字空间. 编码规则可解决的另一问题是访问控制. 在C中,你只能显式说明属性和方法允许访问的 级别.通过名称可比声明时注释更好地表达该意图.这样可更容易地发现代码中对类成员的 非法访问.大部分OO设计区分如下的3级保护: *pivate--只能从类内部访问; *protected--只能由类及其子类访问; *public--随处可用(C中缺省); pivate属性使用双下划线(__foo).注意没有必要在类声明文件(.H文件)中暴露私有方法; 你应该将它们完成隐藏在实现文件中(在.C文件中声明它们为static).protected成员使用单 下划线(_foo, String_Foo).public成员不使用下划线(foo, StringFoo).这样,名称中出现 下划线时,应检查访问权限. 每个类需提供至少一个constructor方法,用于初始化它的属性结构. Constructor调用 应该是初始化的唯一方法.否则,必须暴露对象的内部结构,而损坏包装性. 类可选提供destructor方法,负责释放对象生命期间分配的资源.尽管有多种方法实例化 [instantating]类(接受不同参数的不同constructors), 却只有一种方法消灭对象. 因为constructors&destructors的特殊角色,建议使用一致的命名模式.使用基名"Con" (FooCon1, FooCon2)和"Des"(FooDes). 建议constructor返回初始化属性结构的指针或 NULL.destructor只接受一个参数this,应返回void. 对象分配可静态,动态(在堆中),自动(在栈中).由于C语法市委限制, 你不能在定义点用 constructor call初始化对象.对于静态对象,根本不能调用constructor,因为in a static initializer不允许函数调用.自动对象必须都在block的开始定义,而此时一般没有足够的 初始化信息.因此,不得不将对象分配和初始化分离.你应将对象当做一般的C变量,初始化后 使用.一般当初始化信息可用时,立即初始化对象. 某些对象可能需要析构, 当对象过时或超出作用域时,为每个对象都调用destructor是 种好的编程习惯.后面将看到一个对所有类都可用的virtual destructor. 2.继承性: ------------------------------------------------------------------------------- 继承是一种机制,通过它,可根据已有类定义更专用的新类.子类可以包括父类的 所有属性和方法的定义.子类通过添加自己的属性和方法来扩展父类. 通过将父类的属性结构嵌入作为子类结构的第一个成员,在C中可实现这种类关系.这种 结构方式带来一种属性对齐[alignment],使得子类的指针可安全地转换[upcast]为父类的 指针.特别地,该指针可传给任何期望父类指针的函数.(为了C中严格的正确,应该显式类型 转换[upcast]该指针.)这意味着父类的所有方法都自动适用于子类.换句话说,这些方法被 继承了. 这种简单方法只适用于单继承,因为子类不能同时与多个父类的对齐属性. 我命名继承成员为super,以使类之间的继承关系更明显,并更类似Java. super成员 提供了访问父类属性的句柄[handle].例如,孙类可访问祖类的protected属性_foo,如下: this->super.super._foo. 继承为类的constructor和destructor增加了责任.因为每个子类对象包含嵌入的父类 对象,子类constructor必须照顾初始化父类控制的部分.为了避免潜在的依赖, 在初始化 属性前应先调用父类的constructor.对于析构,则恰好相反,应在最后一步消灭继承部分. 在实现中还采用Java的单抽象基类对象的概念.这意味着任何类都不能定义为独立体 [standalone],而需扩展一些其他的类,Object类在类层次的根部.在这种混合实现(过程 语言添加OO)中,这种设置特别方便, 因为每个对象最终都可被当做Object类的实例--这可 以将对象和所有其他类型清楚地区分开来.这与C++方法不同,C++中每个结构等同于一个类. Object类增加重要的行为,随后被所有其他的类继承,因此实现了[enabling]多态性. 参见object.h和Listing1. 3.多态性 ------------------------------------------------------------------------------- 通过提供继承方法的新实现,扩展类可重载父类的行为.例如,Object类定义了析构函数 Object_Des.String类扩展Object,用自己的析构函数StringDes重载了这一行为.假设需要 删除一个包含一般Object指针的异类包容器[heterogeneous container].因为String从 Object继承而来,一些指针实际指向String对象.若能激发正确实现StringDes来删除String 对象,代码将是多态的.依靠对象(String)的run-time类,而不是指针(Object)的类,多态行为 要求方法决定[resolution],这称为动态绑定. 通过在方法决定中引入间接的附加级别,可以在C中有效地实现动态绑定.不要直接调用 方法(C函数),而使用在类描述器[descriptor]定义的函数指针来调用函数,类描述器可被 每个对象引用.类描述器(有时叫做虚拟表或VTABLE)是对应虚拟函数(有意让子类重载的方法) 的函数指针的记录. 在前面的例子中,Object类的虚拟destructor的实现如下.Object类的类描述器声明了 函数指针Des: struct ObjectClass { ... void (*Des)(struct Object*); }; 类Object的每个实例都包含一个指向该了描述器的指针(叫做虚拟指针或VPTR): struct Object { struct ObjectClass *__vptr; }; 采用如下形式进行虚拟destructor的动态绑定: (*obj->__vptr->Des)(obj); 其中obj指向Object结构. 注意指针obj在这儿被使用了两次:一次用于决定[resolving]方法,一次作为this参数. 动态绑定需要两次的内存访问,比直接函数调用多一次.延迟[late]绑定的内存代价: 在每个 对象中需存储虚拟指针(从Object继承而来),并且每个类需存储一个VTABLE. 类描述器自己可被当做VTABLE类(a class being represented as a VTABLE object) 的单独的实例.因此你可以使用嵌套VTABLEs的技巧来完成虚拟函数的继承.这被封装在宏 VTABLE中.所有类描述器都直接或间接从ObjectClass描述器继承而来,所以都继承了该虚拟 destructor. 继承使虚拟函数调用的语法有点复杂.一般,你需要upcast对象指针(在Object类), downcast虚拟指针__vptr(在特定的类描述器).这些操作,以及双对象指针引用,都封装在 VCALL和END_CALL宏中.例如,采用如下的形式调用对象obj的虚拟destructor: VCALL(obj, Object, Des)END_CALL; 若虚拟函数不只使用this参数,其余的参数应列在END_CALL宏之前.例如: result = VCALL(obj, FooClass, Foo) ,5 ,i+j END_CALL; 其中obj指针指向FooClass或其子类,虚拟函数Foo在FooClass VTABLE中定义. 虚拟表需通过它们的constructor进行初始化. 由VTABLE constructor执行的初始化可 分为两步: 拷贝继承的VTABLE; 重载选定的虚函数实现. 第一步由宏BEGIN_VTABLE自动生成.拷贝继承的VATBLE保证:给子类添加新函数不会破坏 子类, 换句话说,没必要手动修改子类(只需重编译子类代码).除非明确选定一个类来重载 子类的行为, 继承的实现已足够了.当然,若类声明自己的虚函数,相应的函数指针在该步骤 不会被初始化. 第二步将虚函数绑定到其实现,提供VMETHOD和IMETHOD宏来帮助其实现.若你不能提供 给定方法的实现(你想要它是由子类实现的纯虚函数),你仍需用Object_NoIm空实现来初始 化函数指针.Object_NoIm中断执行(通过失败断言[failing assertion]),在运行时帮助 发现未实现的抽象方法. 如前面所讲,每个对象都保留一个指向类描述器的指针(虚指针),这从Object类继承而 来.在constructor中对象初始化时,需正确地设置该指针. 这必须在父类constructor调用 之后完成, 因为父类constructor将设置该指针指向父类VTABLE. 如果正初始化的对象的 VTABLE还未设置,应调用VTBALE constructor.激发VHOOK宏来完成这两步.注意当该父类 constructor作完父类VTABLE同样的事情的时候,整个类层次被正确地初始化. 4.接口 ------------------------------------------------------------------------------- 有时你只需定义对象应支持的抽象方法,而没必要提交[commit to]特定的实现.只根据 接口操作对象, 极大地减少了子系统间的实现依赖性.可重用OO设计的主要原则之一是: 对接口编程,而不对实现编程.Java对接口的支持很好地满足[addresses]了这一设计需求. C中Java风格接口的方法只是VTABLE概念的一般化.如果类只定义抽象方法,而不定义 任何属性怎么办? 这样的类只由它的VTABLE表示.从这样的类继承,只需要维护一个指向相 应VTABLE的指针,并实现所有的抽象方法. 对象可以很容易维护多个这样的指针, 所以从 这样的特殊类的多继承将非常简单. 这没有完成解决内存对齐的问题.在期望接口的地方,不能简单地使用对象指针,因为 没有找到相应的VTABLE.这是因为附加的虚指针不能与从Object继承的虚指针__vptr对齐. 在这种情况下,你仍可添加间接级别来解决问题. 接口和类有很大的不同, 接口不来自Object(因为它们不定义属性),并且它们定义不同 VTABLE(包含__offset域).注意接口定义的虚函数也必须使用该指针,但它应该是一般Object 类型的.你可以用接口编程,而不用管特殊的实现. 5.例子代码 ------------------------------------------------------------------------------- 为了说明已讨论的概念,提供了一个简单类层次的实现.类Shape扩展Object并实现接口 Scalable.这是一个抽象类(只用于继承),所以保护它的constructor和destructor.Shape类 包含String对象作为成员,说明对象合成.Scalable接口只定义一个抽象方法Scale().具体 类Circle和Rect都扩展Shape,并重载Scale()和Area()方法.测试例子将Circle对象分配在 栈帧中,将Rect对象数组分配在堆中.Shape类和Scalable接口的测试函数分别示范类和接口 的动态绑定. 作为练习,你可以修改代码,给Shape类(Object基类)添加属性或虚函数.这些修改不需要 手动修改子类. 6.失去什么 ------------------------------------------------------------------------------- 使用C而非OO语言,会失去什么? 使用本文所讲的技巧,你不必牺牲太多的方便性和表达 力[expressiveness],因为你可以非常容易地将最重要的OO概念映射到C中.也不必损失太多 的可维护性,因为能使许多任务自动化.本实现最重要的特性是: 不需要对子类手动修改, 就可给父类添加新属性和方法(包括虚拟函数和接口). 真正的问题是: 在对象初始化和清除时, C比OO语言要求更严的编程纪律,特别是垃圾 的回收.但C众所周知的缺陷,不容易修复.(例如,C++仍被这些问题困扰.) 在C语言级,封装,继承,和多态性只是种设计模式.和所有的设计模式一样,它们通过 引入特定的命名规则和惯用法[idioms],以带来更高层次的抽象.你可以使用自己的规则, 最重要是要保持一致性,这可极大地改善代码的可读性,并允许快速识别模式. 如果你开始使用这些模式,会完全改变你的C思考方式和编程风格.你的C代码会与Java 更类似. 7.作者 ------------------------------------------------------------------------------- Miro Samek是GE Medical Systems的软件工程师,现在正为诊断x射线装置开发实时嵌入软件. 在波兰Cracow的Jagiellonian大学获得物理博士学位. 曾在德国Darmstadt的GSI从事核物理实验工作. miroslaw.samek@med.ge.com 8.参考文献 ------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 编辑 发表於:2002-01-05 - 10:59:41 IP: 211.97.*.* amine 版主 来自: 发表总数:519 查看   短消息   电子邮件 -------------------------------------------------------------------------------- ******************************************************************************* C中可移植的继承性和多态性2 Fr: [ESP-9712-code] By: Miro Samek Rd: Amine Chen[a_mine@263.net] ******************************************************************************* 快速参考: ========= 本文解释如下这些"设计模式"在C中的实现: 封装性,继承性,多态性. 类继承: 单继承; 接口继承: 多继承. 抽象基类 'Object' ---------------- Object类在类层次的根部.该类封装了虚指针,并定义了虚析构函数.为了效率,将构造和 析构函数定义为'inline'宏.它们都被保护(只能由子类访问),因为该类只想用于继承.客户 不应创建'Object'对象. 该类还声明几个私有方法(某些是类方法,也就是没有'this'指针). 因为这些方法要求 类型转换(只在一定的上下文中合法),所以只通过宏使用它们. 声明类 ------ 1.使用宏CLASS开始类的声明; 2.宏IMPLEMENTS用于想实现的每个接口; 3.声明类属性(实例变量); 4.用宏VTABLE声明虚函数.宏参数必须于CLASS的一致; 5.宏EXTENDS用于IMPLEMENTS声明的每个接口; 6.声明所有虚方法的函数指针; 7.用宏声明类方法; 8.为类实现的所有public&protected方法声明原型. 9.用宏END_CLASS结束类声明. #include "object.h" CLASS(Shape, Object) /* class Shape extends Object */ IMPLEMENTS(Scaleable); /* and implements Scaleable interface */ struct String name; /* public name (object composition) */ VTABLE(Shape, Object) /* Shape's VTABLE extends Object */ EXTENDS(Scaleable); /* and also extends Scaleable */ double (*Area)(Shape); /* virtual method to compute area */ METHODS /* protected constructor (single '_' in the name) * means that Shape is an abstract class */ Shape Shape_Con(Shape this, char *name); void Shape_Des(Shape this); /* protected destructor */ END_CLASS 声明接口 -------- 接口表示为函数指针的结构(VTABLE).接口可扩展其他接口. 1.使用宏INTERFACE开始接口的声明; 2.宏EXTENDS用于想继承的每个接口(可选); 3.声明该接口引入的所有抽象方法指针.每个方法必须声明第一个参数为Object类型; 4.用END_INTERFACE宏结束声明; #include "object.h" INTERFACE(Scaleable) void (*Scale)(Object, double); END_INTERFACE INTERFACE(Fooable) EXTENDS(Scaleable); /* interface Fooable extends Scaleable */ int (*Foo)(Object, long); END_INTERFACE 绑定虚函数 ---------- 虚函数在类描述器(VTABLE)中声明为函数指针.VTABLEs必须为每个类定义和初始化. 分配实现函数给相应的函数指针. 1.使用BEGIN_VTABLE为给定的类定义类描述器constructor; 2.用VMETHOD将抽象类方法绑定到它们的实现; 3.用IMETHOD将抽象接口方法绑定到它们的实现; 4.用END_VTABLE结束虚拟表声明. Example: #include "shape.h" BEGIN_VTABLE(Shape, Object) VMETHOD(Object, Des) = Shape_Des; /* virtual destructor*/ VMETHOD(Shape, Area) = Object_NoIm; /* purely virtual */ IMETHOD(Scaleable, Scale) = Object_NoIm; /* ditto[同上] */ END_VTABLE 定义构造函数 ------------ 每个类必须提供至少一个构造函数,负责属性结构的初始化. 构造函数应有如下的行为: 1.调用基类的构造函数; 2.用VHOOK来赋值虚指针; 3.为所有的接口调用IHOOK,你要重载(调用IMETHOD)这些接口在相应VTABLE中的实现; 4.按类中声明的顺序初始化类属性; Shape Shape_Con(Shape this, char *name) { Object_Con(&this->super); /* superclass constructor */ VHOOK(this, Shape); /* assign 'Shape' VPTR */ IHOOK(this, Shape, Scaleable); /* assign 'Scaleable' VPTR */ if (!StringCon(&this->name, name)) /* construct member */ return NULL; /* signal failure */ return this; /* signal success */ } 定义析构函数 ------------ 类可可选地定义destructor的实现.应在类destructor中执行如下的任务: 1.用与类中声明相反的顺序,删除属性; 2.调用父类destructor; void Shape_Des(Shape this) { StringDes(&this->name); /* member destructor */ Object_Des(&this->super); /* superclass destructor */ } 调用[Invoking]虚Class方法: -------------------------- 可通过VTABLE的虚指针间接调用虚方法. VCALL/END_CALL用来帮助完成虚类方法的调用. #include "shape.h" void testShape(Shape s) { assert(IS_RUNTIME_CLASS(s, Shape)); /* use RTTI in assertion */ printf("Shape.name=\"%s\", Shape.Area()=%.2f\n", StringToChar(&s->name), /* static binding */ VCALL(s, Shape, Area)END_CALL); /* dynamic binding */ } #include "shape.h" Circle c; ... testShape((Shape)c); ... 调用[Invoking]虚Interface方法: ------------------------------ 接口需要解析对实现对象的引用(I_TO_OBJ). ICALL/END_CALL帮助完成虚接口方法的调用. 你可以编写只知道'Scaleable'接口的代码. 通过对Scale()方法的虚函数调用,对象将被调节大小[be scaled]. #include "sclable.h" void testScaleable(Scaleable s) { /* is it an Object?*/ assert(IS_RUNTIME_CLASS(I_TO_OBJ(s), Object)); printf("Scaleable.Scale(), "); ICALL(s, Scale) ,2.0 END_CALL; /* dynamic binding */ } 任何实现'Scaleable'的对象可被用来调用'testScaleable'函数. Circle c; ... testScaleable(&c->super.Scaleable); ... 处理堆上的对象: --------------- 1.用ALLOC/DELETE来分配和删除动态对象; 2.用ALLOC_ARR/DELETE_ARR分配和删除动态对象数组; 用ALLOC/ALLOC_ARR必须调用构造函数初始化. Circ c = ALLOC(Circle); ... DELETE(c); Rect r = ALLOC_ARR(Rect, NRECT); ... DELETE_ARR(r); 使用RTTI (Run-Time Type Information): ------------------------------------- 可使用IS_RUNTIME_CLASS测试对象的类型相容性.不要乱用RTTI,因为它会使多态性无效. 使用虚函数代替. 1.若对象的run-time类是或继承自给定的类,IS_RUNTIME_CLASS返回1; 2.使用I_TO_OBJ从接口引用获得对象引用. Shape s; ... assert(IS_RUNTIME_CLASS(s, Shape)); Scaleable s; ... assert(IS_RUNTIME_CLASS(I_TO_OBJ(s), Object));



关键词: 语言     进行     面向     对象     编程     老站转     使用     实现         

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]