Hello,OOC World![Chap1Sec1-2][DRAFT]
时间:2011-05-16 来源:平繁
项目地址 OOC-GCC : http://code.google.com/p/ooc-gcc/ 源码以LGPL发布,文档为GPL3
转载请注明出处
1.1 什么是OOC
OOC即Object Oritened C,当然也有些人称之为OOPC,多出来的P指Programming.
而OO指面向对象,当然有OB(Object Based)一说,就是基于对象,在这两个名词之间不多
啰嗦,一句话概括的化,OO比OB更“纯”一些,尽管这样概括并不够准确.
本手册所说的面向对象的C主要涉及两个方面,风格与简化!
OOC涉及的两个主要方面
1. 采用何种面向对象的编码风格
2. 如何简化因为使用某种编码风格而产生的额外代码
第一方面不同的人会有不同的见解,虽然千差万别,但却大同小异. 第二方面主要的
实现机制还有两种,一种是通过独立的预处理程序来生成额外的代码,另一种是用C语言本
身支持的宏特性来简化. 前者可以让代码获得更多的特性,后者可能无法很好的简化部
分因为OO风格而产生的冗余代码,但是使用起来却更加方便.
当然准确一些的话OOC还应该有一个较为完整的类型系统,比如GObject库这方面做
的比较全,但是个人认为其前两方面做得不怎么样,所以用纯C开发起来并不方便.
本手册主要是围绕一个名为OOC-GCC的小项目展开的,自然介绍的是其中采用的OO风
格,而这个项目本身针对更多是纯C的开发, 并且在使用上尽力做到简易,所以采用了宏来
完成“偷懒”的工作.
1.2 面向对象中常涉及的概念
1. 类,成员,接口与方法
2. 包,模块,命名空间,访问权限
3. 重载,重写,继承,多态,泛型,反射与自省
上面写的东西可能不全面,但即使这些展开说也多了去了,而且具体到某些细节我 的理解也不一定全都正确. 所以一来为了简洁,二来为了不去一不小心误人子弟.这些东 西我争取用一两句话来介绍.而因为本手册是针对C来使用的,也会给出一些在C语言里的 对应. 类及类成员是啥我就不多说了,就像C中的结构与结构成员.接口与方法到C里面都指 函数.tmd也不知哪个龟孙开始提出的OO,整出这么多概念来. C语言常用结构体来模拟 类,自然普通结构成员就对应类成员,而类的方法采用函数指针来模拟,个人的理解是这种 指针的模拟都对应接口或是虚方法. 当我们把某个函数指针指向具体的函数,就相当于 真正的实现了某个接口或是将虚方法化虚为实. C语言里没有什么包,命名空间等这类东西,不过模块化的C一般就是采用独立文件 的编写方式. 常把一个模块放在一个.c和一个.h文件中,不同的模块经过编译产生了 众多的.o文件,再通过链接将各个.o统一起来. 关于访问权限,一般的OO语言常涉 及public,private,protected这些,主要是限制是类中访问与类外访问以及继承时的权 限. C语言可没这么多条条框框,大体上除了局部括号内受限以及static关键字限定某些 文件内访问.其它的都很自由,你自己掌控一切. 关于继承,我见过两种描述,当然个人觉得这两种说法并不冲突,只是侧重点不太 相同. 一种感觉重在描述“数据抉择”这一块,就是如何去使用数据. 其说法是子类 使用父类的方法这是继承,而子类实现父类的接口在“向上转型”成父类再来使用以获得 不同的实现这是多态(的一种体现). 另一种关于继承的解释重在“数据封装”,就是继承 就是子类包含父类的东西,从本之上说是一种包含关系. 个人比较喜欢从“数据封装”方面来阐述继承,因为这更和字面意思,和现实生活也 更加对应.比如儿子继承母亲的某些性格特征, 继承遗产等等,侧重的也是“数据封装”. 当然现实中的继承远比程序中的复杂,而且很多现实继承是那种父类大,子类小,子类局部 继承的情况. 而程序中的继承基本都是由小到大的模式,这就要求我们进一步分析并 采用一些模式来完成数据的抽象. 从“数据封装”方面来看代码中的继承,大体也有两种.一种是一般成员继承, 这 种就是前面从“数据抉择”上的通过子类使用父类,另一种是接口等的继承,这种具体 实现是在子类中完成的, 但使用时以父类的形式使用,如前所述“数据抉择”上通过父类 使用子类的情形,一个父类定义接口,有很多子类的不同实现,这就体现了多态这一概念. 用C语言来完成“数据封装”上的继承,一种常规方式是子类结构体包含父类结构体 的形式.并且把父类结构体放在子类结构体的首部, 这样做的一个好处是当我们在堆上分 配一个对象,只需强制转换其指针到父类指针形态,就可以按照父类来使用了,当然这种 使用本身看上去是通过父类使用子类,也就是多态(的一种). 接下来说一下重载重写等相关的定义,这一块我直接用英文了(因为我有些整体相当不 错的书的中译这一块翻译上总有差异), 就是overload,override,overwrite(还有人 有时还会用到一个词overlap).关于这一块我想多说几句,就是关于这几个词不同语言上 也可能有一些细微的差异. 而且很多所谓的OO语言对这一块虽然支持,但并不完美,尤其 体现在overload运算符以及返回值的处理的支持上. 简单说overload是允许某类中名 称相同但参数等不同的方法的存在,这需要编译器或解释器能够通过传入参数等进行判断 进而确定到底是用哪个方法. override和overwrite都是子类中有和父类重名的方法, 不同的是overwrite会抹杀掉父类的方法,而override只是隐藏了父类的方法. 从C语言 来说,因为方法都是用函数指针来模拟,所以这些都可以模拟,但是都不是自动的,需要相 当的额外代码来辅助,甚至是需要构件一个抽象类作为中间层来模拟. 和overXXXX系的对比着可以说一下的就是泛型,针对的是几个不同参数的方法,让编 译器根据参数等来判断.泛型通常是只写一个方法,但是参数采用抽象的形式, 编译器通 过类型推衍等来判断具体是什么数据(当然这么概括泛型是为了简便,可能不太全面). 最后说一下反射(reflection)和自省(introspection),这是更加动态的机制,可能 需要类型系统等的支持.这两个概念有些人分不清,也有些人干脆把他们揉在一起来理解, 不过这也不影响,只要知道其动态特性怎么用就好. 简单的说反射式通过一个动态的字 符串,获得相应的类或方法等.自省则是通过一个已有的但未知类型的实例直接获得其相 关信息或是直接使用.C99好像支持了__TYPEOF__(gnu套件一般用小写的typeof),有点 自省的意思了. 但是去模拟反射却不容易,需要额外的数据结构以及约定好的类型系统 库才行(个人觉得).还有要说的是反射和自省看似美丽但是用起来代价也是不小的,比如 额外引入的数据处理对性能影响比较大, 很多时候使用这些机制会将代码变得冗长难懂, 类型不安全等. 广义上讲多态就是只从多种数据中选择需要的适合的来用,从这个角度来说它与前面 说的“两大部分”中“数据抉择”部分直接对应了.这样理解的话,它就包含了前面说 的overload,泛型,反射,自省等等. 而广义上的继承则也占据了“数据封装”中的相当 一部分.因此某种意义上说广义的继承和广义的多态就是OO的核心(不要动不动就下定 义,所以我加了个限定词“某种意义上”) 用C去模拟OO,其实无论“数据封装”或是“数据抉择”哪一部分都能模拟,只是模拟 的时候额外的代码量也是相当可观的. 而单从用宏来简化这些额外代码上,主要能简化 的就是“数据封装”的部分,“数据抉择”部分如果想要简化最好还是通过间接代码生成 的方式. 也因此OOC-GCC中的宏主要是在解决“数据封装”方面的问题,对于“数据抉 择”方面的OO模拟还是要写不少额外的代码(最近测试了一些和完全对应的C++代码 编译出来大小相差不大,相对还小些,特别使用到堆内存的时候).
注意
上面的概念可能有不少地方不够准确或是存在错误, 如果有所发现, 还请联系 我[email protected]. 另外后面的章节尽量不再解释和OO相关的抽象概念,如有需要, 请参阅此节.
上面写的东西可能不全面,但即使这些展开说也多了去了,而且具体到某些细节我 的理解也不一定全都正确. 所以一来为了简洁,二来为了不去一不小心误人子弟.这些东 西我争取用一两句话来介绍.而因为本手册是针对C来使用的,也会给出一些在C语言里的 对应. 类及类成员是啥我就不多说了,就像C中的结构与结构成员.接口与方法到C里面都指 函数.tmd也不知哪个龟孙开始提出的OO,整出这么多概念来. C语言常用结构体来模拟 类,自然普通结构成员就对应类成员,而类的方法采用函数指针来模拟,个人的理解是这种 指针的模拟都对应接口或是虚方法. 当我们把某个函数指针指向具体的函数,就相当于 真正的实现了某个接口或是将虚方法化虚为实. C语言里没有什么包,命名空间等这类东西,不过模块化的C一般就是采用独立文件 的编写方式. 常把一个模块放在一个.c和一个.h文件中,不同的模块经过编译产生了 众多的.o文件,再通过链接将各个.o统一起来. 关于访问权限,一般的OO语言常涉 及public,private,protected这些,主要是限制是类中访问与类外访问以及继承时的权 限. C语言可没这么多条条框框,大体上除了局部括号内受限以及static关键字限定某些 文件内访问.其它的都很自由,你自己掌控一切. 关于继承,我见过两种描述,当然个人觉得这两种说法并不冲突,只是侧重点不太 相同. 一种感觉重在描述“数据抉择”这一块,就是如何去使用数据. 其说法是子类 使用父类的方法这是继承,而子类实现父类的接口在“向上转型”成父类再来使用以获得 不同的实现这是多态(的一种体现). 另一种关于继承的解释重在“数据封装”,就是继承 就是子类包含父类的东西,从本之上说是一种包含关系. 个人比较喜欢从“数据封装”方面来阐述继承,因为这更和字面意思,和现实生活也 更加对应.比如儿子继承母亲的某些性格特征, 继承遗产等等,侧重的也是“数据封装”. 当然现实中的继承远比程序中的复杂,而且很多现实继承是那种父类大,子类小,子类局部 继承的情况. 而程序中的继承基本都是由小到大的模式,这就要求我们进一步分析并 采用一些模式来完成数据的抽象. 从“数据封装”方面来看代码中的继承,大体也有两种.一种是一般成员继承, 这 种就是前面从“数据抉择”上的通过子类使用父类,另一种是接口等的继承,这种具体 实现是在子类中完成的, 但使用时以父类的形式使用,如前所述“数据抉择”上通过父类 使用子类的情形,一个父类定义接口,有很多子类的不同实现,这就体现了多态这一概念. 用C语言来完成“数据封装”上的继承,一种常规方式是子类结构体包含父类结构体 的形式.并且把父类结构体放在子类结构体的首部, 这样做的一个好处是当我们在堆上分 配一个对象,只需强制转换其指针到父类指针形态,就可以按照父类来使用了,当然这种 使用本身看上去是通过父类使用子类,也就是多态(的一种). 接下来说一下重载重写等相关的定义,这一块我直接用英文了(因为我有些整体相当不 错的书的中译这一块翻译上总有差异), 就是overload,override,overwrite(还有人 有时还会用到一个词overlap).关于这一块我想多说几句,就是关于这几个词不同语言上 也可能有一些细微的差异. 而且很多所谓的OO语言对这一块虽然支持,但并不完美,尤其 体现在overload运算符以及返回值的处理的支持上. 简单说overload是允许某类中名 称相同但参数等不同的方法的存在,这需要编译器或解释器能够通过传入参数等进行判断 进而确定到底是用哪个方法. override和overwrite都是子类中有和父类重名的方法, 不同的是overwrite会抹杀掉父类的方法,而override只是隐藏了父类的方法. 从C语言 来说,因为方法都是用函数指针来模拟,所以这些都可以模拟,但是都不是自动的,需要相 当的额外代码来辅助,甚至是需要构件一个抽象类作为中间层来模拟. 和overXXXX系的对比着可以说一下的就是泛型,针对的是几个不同参数的方法,让编 译器根据参数等来判断.泛型通常是只写一个方法,但是参数采用抽象的形式, 编译器通 过类型推衍等来判断具体是什么数据(当然这么概括泛型是为了简便,可能不太全面). 最后说一下反射(reflection)和自省(introspection),这是更加动态的机制,可能 需要类型系统等的支持.这两个概念有些人分不清,也有些人干脆把他们揉在一起来理解, 不过这也不影响,只要知道其动态特性怎么用就好. 简单的说反射式通过一个动态的字 符串,获得相应的类或方法等.自省则是通过一个已有的但未知类型的实例直接获得其相 关信息或是直接使用.C99好像支持了__TYPEOF__(gnu套件一般用小写的typeof),有点 自省的意思了. 但是去模拟反射却不容易,需要额外的数据结构以及约定好的类型系统 库才行(个人觉得).还有要说的是反射和自省看似美丽但是用起来代价也是不小的,比如 额外引入的数据处理对性能影响比较大, 很多时候使用这些机制会将代码变得冗长难懂, 类型不安全等. 广义上讲多态就是只从多种数据中选择需要的适合的来用,从这个角度来说它与前面 说的“两大部分”中“数据抉择”部分直接对应了.这样理解的话,它就包含了前面说 的overload,泛型,反射,自省等等. 而广义上的继承则也占据了“数据封装”中的相当 一部分.因此某种意义上说广义的继承和广义的多态就是OO的核心(不要动不动就下定 义,所以我加了个限定词“某种意义上”) 用C去模拟OO,其实无论“数据封装”或是“数据抉择”哪一部分都能模拟,只是模拟 的时候额外的代码量也是相当可观的. 而单从用宏来简化这些额外代码上,主要能简化 的就是“数据封装”的部分,“数据抉择”部分如果想要简化最好还是通过间接代码生成 的方式. 也因此OOC-GCC中的宏主要是在解决“数据封装”方面的问题,对于“数据抉 择”方面的OO模拟还是要写不少额外的代码(最近测试了一些和完全对应的C++代码 编译出来大小相差不大,相对还小些,特别使用到堆内存的时候).
注意
上面的概念可能有不少地方不够准确或是存在错误, 如果有所发现, 还请联系 我[email protected]. 另外后面的章节尽量不再解释和OO相关的抽象概念,如有需要, 请参阅此节.
相关阅读 更多 +