软件设计本质论(Essential Design) —白话面向对象
软件设计本质论(Essential Design)—白话面向对象
转载时请注明出处:http://blog.csdn.net/absurd/
不同的人在谈面向对象编程(OOP)时所指的含义并不相同。有人认为任何采用图形界面的应用程序都是面向对象的。有人把它作为术语来描述一种特别的进程间通信机制。还有人使用这个词汇是另有深义的,他们其实是想说:“来啊,买我的产品吧!”我一般不提OOP,但只要提到,我的意思是指使用继承和动态绑定的编程方式。 --《C++沉思录》
《C++沉思录》说的是十几年前的事了,现在大家对面向对象的回答已经是众口一词:封装、继承和多态。大家都知道,在面向对象中,一辆汽车是一个对象,汽车这个概念是一个类。汽车有漂亮的外观,把各种内部原理都隐藏起来了,司机不必知道它的内部工作原理仍然能开车,即使汽车随技术的进步不断升级,对司机也没有什么影响,这就是封装的好处。
汽车是交通工具的一种,汽车是一个类,交通工具也是一个类,而交通工具类包括了汽车类,从而具有更广泛的意义。这种从抽象到具体的关系就是继承关系,我们可以说汽车类继承了交通工具类,汽车类是交通工具类的子类,交通工具类是汽车类的父类。
作为交通工具,它肯定可以运动(move),从甲地运动到乙地,就起到了交通的作用。轮船是一种交通工具,所以轮船类也是交通工具类的子类。同样是运动,轮船的运动和汽车的运动方式肯定有所不同,这样以不同的方式完成同样的功能就叫多态。
关于对象:对象就是某一具体的事物,比如一个苹果,一台电脑都是一个对象。每个对象都是唯一的,两个苹果,无论它们的外观有多么相像,内部成分有多么相似,两个苹果毕竟是两个苹果,它们是两个不同的对象。对象可以是一个实物,也可能是一个概念,比如某个苹果对象是实物,而一项政策可能就是一个概念性的对象了。
关于类:对象可能是一个无穷的集合,用枚举的方式来表示对象集合不太现实。抽象出对象的特征和功能,按此标准将对象分类,这就引入类的概念。类就是一类事物的统称,类实际上就是一个分类的标准,符合这个分类标准的对象都属于这个类。当然,为了方便起见,通常只需要抽取那些,对当前应用来说是有用的特征和功能。
关于抽象类:类是对对象的抽象,比如,苹果是对所有具体的苹果的抽象。如果我们对苹果这个类进行一步抽象,可以得到一个水果类。这种对类本身进行抽象而得到的类,就是抽象类。抽象类不像普通类,它是没有对象与之对应的。像苹果类,你总是可以拿到一个叫苹果的东西,而对于水果类,根本没一个真正叫水果的东西。你可以说一个苹果是一个水果,从逻辑上讲没有错,但没有什么意义。一般在程序中,抽象类是不能实例化的。
关于面向对象:面向对象就是以对象为中心。为什么不说是面对类,而说是面向对象呢?类是对象的集合,考虑类实际上也是在考虑对象,有时甚至并不严格的区分它们。所以说面向对象一词比面向类更确切。
既然以对象为中心,面向对象所考虑的内容自然是对象、对象间的协作、对象的分类、类之间的关系等等,由此引申了出几个重要的概念。
1. 封装
what:对象也有隐私,对象的隐私就是对象内部的实现细节。要想对象保持良好的形象就要保护好对象隐私,所谓的封装其实就是保护对象隐私。当然,没有人能完全隐藏自己的隐私,比如你去转户口时,你不得不透露自己的家庭信息和健康状况。另外,在不同的场合所透露隐私的数量也不一样,朋友和家人可能会知道你更多隐私,同事次之,其他人则知道得更少。面向对象也考虑了这些实际的情况,所以像C++之类的语言有public/private/protected/friend等关键字,以适应于不同的情况。
why:封装可以隔离变化。据以往的经验,我们知道内部实现是容易变化的,比如电脑在不断的升级,机箱还是方的,但里面装的CPU和内存已是今非昔比了。变化是不可避免的,但变化所影响的范围是可以控制的,不管CPU怎么变,它不应该影响用户使用的方式。封装是隔离变化的好办法,用机箱把CPU和内存等等封装起来,对外只提供一些标准的接口,如USB插口、网线插口和显示器插口等等,只要这些接口不变,内部怎么变,也不会影响用户的使用方式。
封装可以提高易用性。封装后只暴露最少的信息给用户,对外接口清晰,使用更方便,更具用户友好性。试想,如果普通用户都要知道机箱内部各种芯片和跳线,那是多么恐怖的事情,到现在为止我甚至还搞不清楚硬盘的跳线设置,幸好我没有必要知道。
how:在C语言中,可以用结构+函数来模拟类的实现,而用这种结构定义的变量就是对象。封装有两层含义,其一是隐藏内部行为,即隐藏内部函数,调用者只能看到对外提供的公共函数。其二是隐藏内部信息,即隐藏内部数据成员。现在都建议不要对外公开任何数据成员,即使外部需要知道的数据成员,也只能通过函数获取。
在C语言中要隐藏内部函数很简单:不要它把放在头文件中,在C文件中定义时,前面加static关键字,每个类放在独立的文件中。这样可以把函数的作用范围限于当前文件内,当前文件只有类本身的实现,即只有当前的类自己才能看到这些函数,这就达到了隐藏的目的。
在C语言中要隐藏数据成员较为麻烦,它没有提供像C++中所拥有的public/protected/friend/private类似的关键字。只能通过一些特殊方法模拟部分效果,我常用的方法有两种。
其一是利用C的特殊语法,在头文件中提前声明结构,在C文件才真正定义它。这样可以把结构的全部数据信息都隐藏起来。因为外部不知道对象所占内存的大小,所以不能静态的创建该类的对象,只能调用类提供的创建函数才能创建。这种方法的缺陷是不支持继承,因为子类中得不到任何关于父类的信息。如:
头文件:
struct_LrcPool; typedefstruct_LrcPoolLrcPool;
LrcPool*lrc_pool_new(size_tunit_size,size_tn_prealloc_units); void* lrc_pool_alloc(LrcPool*thiz); void lrc_pool_free(LrcPool*thiz, void*p); void lrc_pool_destroy(LrcPool*thiz);
|
C文件:
struct_LrcPool { size_tunit_size; size_tn_prealloc_units; };
LrcPool*lrc_pool_new(size_tunit_size,size_tn_prealloc_units) { LrcPool* thiz = LRC_ALLOC(LrcPool, 1);
if(thiz !=NULL) { thiz->unit_size =unit_size; thiz->n_prealloc_units =n_prealloc_units; }
returnthiz; } |
其二是把私有数据信息放在一个不透明的priv变量中。只有类的实现代码才知道priv的真正定义。如:
头文件:
struct_LrcBuilder { LrcBuilderBegin on_begin; LrcBuilderOnIDTag on_id_tag; LrcBuilderOnTimeTag on_time_tag; LrcBuilderOnLrc on_lrc; LrcBuilderEnd on_end; LrcBuilderDestroy destroy;
charpriv[1]; };
|
C文件:
struct_LrcDumpBuilder { FILE* fp; };
typedefstruct_LrcDumpBuilderLrcDumpBuilder; LrcBuilder*lrc_dump_builder_new(FILE*fp) { LrcDumpBuilder* data = NULL; LrcBuilder* thiz = (LrcBuilder*)calloc(sizeof(LrcBuilder) +sizeof(LrcDumpBuilder), 1);
if(thiz !=NULL) { thiz->on_begin = lrc_dump_builder_on_begin; thiz->on_id_tag = lrc_dump_builder_on_id_tag; thiz->on_time_tag = lrc_dump_builder_on_time_tag; thiz->on_lrc = lrc_dump_builder_on_lrc; thiz->on_end = lrc_dump_builder_on_end; thiz->destroy = lrc_dump_builder_destroy; data = (LrcDumpBuilder*)thiz->priv; data->fp =fp != NULL ?fp : stdout; }
returnthiz; } |
2. 继承
what: 继承描述的是一种抽象到具体的关系。具体的东西继承了抽象的东西的特性,比如说,水果这个概念比苹果这个概念更抽象,其意义更具有一般性,而苹果这个概念则更具体,其意义更狭窄一些,在面向对象里,我们可以说苹果类继承了水果类。继承是指继承了父类的特性,继承本质是源于分类学,细的分类继承大分类的特性。
why: 继承描述了抽象到具体的关系,所以能够有效利用抽象这件武器来战胜软件的复杂性。抽象在实现中无处不在,类就是对事物的抽象,提到苹果你就想到苹果这一类事物,无需要关心其大小、颜色和成分,苹果这两个字就足够了。名不正则言不顺,言不顺则事不成,看来老夫子已经领悟到了抽象的威力。
继承不但利用了抽象的力量来降低系统的复杂性,它还提供了一种重用的方式。假设我们承认下列面这个继承关系,苹果继承了水果,水果继承了食物,如果我们已经知道什么是食物,什么是水果,在描述苹果时,没有必要去重复讲解食物和水果的概念了,这就是重用,重用了对水果和食物两个概念的理解。
how: 在C语言中实现继承很简单,可以用结构来模拟。这种实现基于一个明显的事实,结构在内存中的布局与结构的声明具有一致的顺序。我们知道在程序描述事物的特征时,主要通过数据变量描述事物的属性特征,如颜色、重量和体积等,用函数来描述事物的行为特征,和运动、成长和搏斗等。在C语言中实现继承的方式如:
struct _GObject { GTypeInstance g_type_instance; volatile guint ref_count; GData *qdata; };
struct_GtkObject { GObject parent_instance; guint32 flags; };
struct_GtkWidget { GtkObject parent_instance; guint16 private_flags; guint8 state; guint8 saved_state; gchar *name; GtkStyle *style; GtkRequisition requisition; GtkAllocation allocation; GdkWindow *window; GtkWidget *parent; };
|
(GtkWidget继承GtkObject,GtkObject继承GObject)
继承在现实世界中应用很广,在程序里也是一样,甚至可以说是过度使用了。多年以前一些大师已经提出,优先使用组合而不是继承。主要原因有三点,首先是多级继承和多重继承太复杂了,失去了抽象带来的简洁性。其次是父类与子类之间共享太多信息,它们的耦合太紧密。三是父类与子类之间的关系在编译时就静态绑定了,很难做到在运行时多态。
现在一般都提倡,只继承接口不继承实现,通过组合达到代码重用的目的。在《设计模式》中是这样强调的,在MS的COM里也是这样做的。所以我基本上只使用接口继承,很少遇到什么麻烦,建议大家也遵循这一准则。
3. 多态
what: 尽管多态这个词本身就表明了它所代表的意义,但还是让初学者感到多少有些神秘。多态就是完成相同功能的多种方式,比如拿动物的运动来说吧,鸟的运动通常是飞,鱼的运动通常是游,陆上动物的运动通常是跑,同是运动,但方式不一样,这就是多态。不少人对多态的回答是,允许同名函数存在。这种回答显然没有抓住多态的本质。
why: 关于动物运动这个例子,可能无法展示多态的好处。我们来考虑另外一个多态的例子,U盘。U盘的技术含量可能不是很高,有很多厂家都在设计和生产,就是说U盘有多种不同的实现,这就是U盘的多态。U盘的多态性对消费者来说是有好处的,选择多了,你可以在价格、质量和外观等方式做出平衡,选择你中意的U盘。多态的前提是接口的一致性,否则多态造成的麻烦远胜于它带来的好处。不管U盘的体积、颜色和质量如何,它都必需遵循相应的USB标准,这些U盘在任何带USB接口的电脑上都可以使用。
how: 多态在C语言中通常用函数指针来实现,函数指针定义了函数的原型,即它的参数和返回值的描述,以及函数的意义,不同的函数可以有相同的函数原型,比如排序函数,无论是快速排序还是归并排序,它们的实现不一样,但函数原型可以一样。在不同的情况下,让函数指针到不同的函数实现上,这就实现了多态。下面的C语言例子:
接口:
struct_LrcBuilder; typedefstruct_LrcBuilderLrcBuilder;
typedef LRC_RESULT (*LrcBuilderBegin)(LrcBuilder*thiz, constchar*buffer); typedef LRC_RESULT (*LrcBuilderOnIDTag)(LrcBuilder*thiz, constchar*key, size_tkey_length, constchar*value, size_tvalue_length); typedef LRC_RESULT (*LrcBuilderOnTimeTag)(LrcBuilder*thiz, size_tstart_time); typedef LRC_RESULT (*LrcBuilderOnLrc)(LrcBuilder*thiz, constchar*lrc, size_tlrc_length); typedef LRC_RESULT (*LrcBuilderEnd)(LrcBuilder*thiz); typedef LRC_RESULT (*LrcBuilderDestroy)(LrcBuilder*thiz);
struct_LrcBuilder { LrcBuilderBegin on_begin; LrcBuilderOnIDTag on_id_tag; LrcBuilderOnTimeTagon_time_tag; LrcBuilderOnLrc on_lrc; LrcBuilderEnd on_end; LrcBuilderDestroy destroy;
charpriv[1]; }; |
实现一:
LrcBuilder*lrc_dump_builder_new(FILE*fp) { LrcDumpBuilder* data = NULL; LrcBuilder* thiz = (LrcBuilder*)calloc(sizeof(LrcBuilder) + sizeof(LrcDumpBuilder), 1);
if(thiz !=NULL) { thiz->on_begin = lrc_dump_builder_on_begin; thiz->on_id_tag = lrc_dump_builder_on_id_tag; thiz->on_time_tag = lrc_dump_builder_on_time_tag; thiz->on_lrc = lrc_dump_builder_on_lrc; thiz->on_end = lrc_dump_builder_on_end; thiz->destroy = lrc_dump_builder_destroy; data = (LrcDumpBuilder*)thiz->priv; data->fp =fp != NULL ?fp : stdout; }
returnthiz; } |
实现二:
LrcBuilder*lrc_default_builder_new(void) { LrcDefaultBuilder* data = NULL; LrcBuilder* thiz = (LrcBuilder*)calloc(sizeof(LrcBuilder) +sizeof(LrcDefaultBuilder), 1);
if(thiz !=NULL) { thiz->on_begin = lrc_default_builder_on_begin; thiz->on_id_tag = lrc_default_builder_on_id_tag; thiz->on_time_tag = lrc_default_builder_on_time_tag; thiz->on_lrc = lrc_default_builder_on_lrc; thiz->on_end = lrc_default_builder_on_end; thiz->destroy = lrc_default_builder_destroy; data = (LrcDefaultBuilder*)thiz->priv; }
returnthiz; } |
类的三个层次:
类这个概念比较微妙,即使在软件开发领域,不同的人提到这个概念所指的内容也不一样。一些大师早就注意到了这一点,为了让这个概念在不同情况下,具有较准确的意义,他们建议从三个层次看待类这个概念:
1. 概念层(Conceptual)
这是一个较高的层次,通常在进行领域分析时,为了建立概念模型时使用。这时使用的术语是现实世界中的术语,而不是软件开发中的术语。在这个层次,类只是一个概念,加上一些不太严谨的特征说明,甚至只有一个名称。尽管它往往与软件开发中的类一一对应,便这种映射并不一定是直接的。
2. 规格层(Specification)
在这个层次,类已经是属于软件开发范畴了,但主要关注的是类的接口,而不是类的实现。此时你可能想到它的一组接口函数,而不关心这些函数是如何实现的。
3. 实现层(Implementation)
在这个层次,我们才真正关注类的实现,此时你可能会想到一些用某种语言写成的函数体,定义的成员变量等等。
面向对象的好处:
面向对象已经征服了软件开发的绝大部分领域,近几年来出现的面向方面的编程(AOP)、产生式编程(GP)和面向组件的开发等等,都提出了一些新的思维,在某些方面大提高了开发效率,但它们并非是取代了面向对象,相反是对面向对象的补充和完善,面向对象始终稳坐第一把交椅。
面向对象到底有何德何能,它凭借什么取代面向对程呢?封装、继承和多态到底有何种魔力,吸引众多的高手去完善它,让布道者们不厌其烦的颂扬它呢?归根结底,面向对象会带来两个好处,这两个好处正是设计者们一直在追求的:
1. 降低系统的复杂度。
众所周知,随着硬件的飞速发展,计算机的计算能力越来越强大,人们对软件期望也越来越高,而软件复杂度又与它的规模成指数倍数增长。软件复杂度可以说是软件开发的第一大难题,我们可以轻而易举的写出5000行代码,而面对100万行代码规模的软件,会有点让人觉得人的智力是多么有限。
而面向对象正是降低系统复杂度的好方法。首先它按类来组织系统,把系统分成几个大的部分,每个部分又由更小的子类组成,如此细分下去直到我们能轻易实现它为此,这种分而治之的方法符合人类解决复杂问题的习惯。
其次是它采用从抽象到具体的顺序来把握事物,抽象让我们用少量精力先掌握事物的共性,然后再去研究事物更具体的特性,这种逐渐细化的方法也是符合人类解决复杂问题的习惯的。
2. 隔离变化。
需求变化和技术变化也是软件开发所面临的两大难题。用户似乎从来不知道什么是他们真正的需求,或许他们真正的需求也是在变化的。技术可谓日新月异,我们不断的发明新技术,这些技术帮我们提高了生产力。但有时新技术也会砸到自己的脚,为了运用这些新技术,我们要花更多时间学习和工作。或者说创新只是满足了少数人的乐趣,让多数人吃了苦头。
只有变化才是永恒的,诅咒变化无济于事,面对它并搞掂它才是正途。大师也建议我们去拥抱变化,而不是逃避变化。面向对象正是拥抱变化的银弹之一,它不是尝试阻止变化的发生(对此谁也无能为力),而是努力去隔离变化。与整体比较,变化的部分毕竟只占很小的分量,如果这些变化被隔离开了,它不影响不变的部分的,变的部分独立变化,不会牵一发而动全身,这可以大大减少变化引起的麻烦。针对接口编程而不是针对实现编程,这是面对象的精髓,也是拥抱变化的良方。
~~~end~~
转载自http://blog.csdn.net/absurd/article/details/1067390
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
1、 概述
C语言是一种面向过程的程序设计语言,而C++是在C语言基础上衍生来了的面向对象的语言,实际上,很多C++实现的底层是用C语言实现的,如在Visual C++中的Interface其实就是struct,查找Interface的定义,你可以发现有这样的宏定义:
#ifndef Interface
#define Interface struct
#endif
C++在语言级别上添加了很多新机制(继承,多态等),而在C语言中,我们也可以使用这样的机制,前提是我们不得不自己实现。
本文介绍了用C语言实现封装,继承和多态的方法。
2、 基本知识
在正式介绍C语言实现封装,继承和多态事前,先介绍一下C语言中的几个概念和语法。
(1) 结构体
在C语言中,常把一个对象用结构体进行封装,这样便于对对象进行操作,比如:
1
2
3
4
5
6
7
|
strcut Point{
int
x;
int
y;
};
|
结构体可以嵌套。因而可以把一个结构体当成另一个结构体的成员,如:
1
2
3
4
5
6
7
|
struct
Circle {
struct
Point point_;
int
radius;
};
|
该结构体与以下定义完全一样(包括内存布置都一样):
1
2
3
4
5
6
7
8
9
|
struct
Circle {
int
x;
int
y;
int
radius;
};
|
(2) 函数指针
函数指针是指针的一种,它指向函数的首地址(函数的函数名即为函数的首地址),可以通过函数指针来调用函数。
如函数:
int func(int a[], int n);
可以这样声明函数指针:
int (*pFunc)(int a[], int n);
这样使用:
pFunc = func;
(*pFunc)(a, n);【或者PFunc(a, n)】
可以用typedef定义一个函数指针类型,如:
typdef int (*FUNC)(int a[], int n)
可以这样使用:
int cal_a(FUNC fptr, int a[], int n)
{
//实现体
}
(3) extern与static
extern和static是C语言中的两个修饰符,extern可用于修饰函数或者变量,表示该变量或者函数在其他文件中进行了定义;static也可用于修饰函数或者变量,表示该函数或者变量只能在该文件中使用。可利用它们对数据或者函数进行隐藏或者限制访问权限。
3、 封装
在C语言中,可以用结构+函数指针来模拟类的实现,而用这种结构定义的变量就是对象。
封装的主要含义是隐藏内部的行为和信息,使用者只用看到对外提供的接口和公开的信息。有两种方法实现封装:
(1) 利用C语言语法。在头文件中声明,在C文件中真正定义它。
这样可以隐藏内部信息,因为外部不知道对象所占内存的大小,所以不能静态的创建该类的对象,只能调用类提供的创建函数才能创建。这种方法的缺陷是不支持继承,因为子类中得不到任何关于父类的信息。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
//头文件:point.h
#ifndef POINT_H
#define POINT_H
struct
Point;
typedef
struct
Point point;
point * new_point();
//newer a point object
void
free_point(point *point_);
// free the allocated space
#endif
//C文件:point.c
#include”point.h”
strcut Point
{
int
x;
int
y;
};
point * new_point()
{
point * new_point_ = (point *)
malloc
(
sizeof
(point));
return
new_point_;
}
void
free_point(point *point_)
{
if
(point_ == NULL)
return
;
free
(point_);
}
|
(2) 把私有数据信息放在一个不透明的priv变量或者结构体中。只有类的实现代码才知道priv或者结构体的真正定义。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
#ifndef POINT _H
#define POINT_H
typedef
struct
Point point;
typedef
struct
pointPrivate pointPrivate;
strcut Point
{
Struct pointPrivate *pp;
};
int
get_x(point *point_);
int
get_y(point *point_);
point * new_point();
//newer a point object
void
free_point(point *point_);
// free the allocated space
#endif
//C文件:point.c
#include”point.h”
struct
pointPrivate
{
int
x;
int
y;
}
int
get_x(point *point_)
{
return
point_->pp->x;
}
int
get_y(point *point_)
{
return
point_->pp->y;
}
//others…..
|
4、 继承
在C语言中,可以利用“结构在内存中的布局与结构的声明具有一致的顺序”这一事实实现继承。
比如我们要设计一个作图工具,其中可能涉及到的对象有Point(点),Circle(圆),由于圆是由点组成的,所有可以看成Circle继承自Point。另外,Point和Circle都需要空间申请,空间释放等操作,所有他们有共同的基类Base。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
//内存管理类new.h
#ifndef NEW_H
#define NEW_H
void
*
new
(
const
void
*
class
, ...);
void
delete
(
void
* item);
void
draw (
const
void
* self);
#endif
//内存管理类的C文件:new.c
#include “new.h”
#include “base.h”
void
*
new
(
const
void
* _base, ...)
{
const
struct
Base * base = _base;
void
* p =
calloc
(1, base->size);
assert
(p);
* (
const
struct
Base **) p = base;
if
(base ->ctor)
{
va_list
ap;
va_start
(ap, _base);
p = base ->ctor(p, &ap);
va_end
(ap);
}
return
p;
}
void
delete
(
void
* self)
{
const
struct
Base ** cp = self;
if
(self && * cp && (* cp) —> dtor)
self = (* cp) —>dtor(self);
free
(self);
}
void
draw (
const
void
* self)
{
const
struct
Base *
const
* cp = self;
assert
(self &&* cp && (* cp)->draw);
(* cp) ->draw(self);
}
//基类:base.h
#ifndef BASE_H
#define BASE_H
struct
Base
{
size_t
size;
//类所占空间
void
* (* ctor) (
void
* self,
va_list
* app);
//构造函数
void
* (* dtor) (
void
* self);
//析构函数
void
(* draw) (
const
void
* self);
//作图
};
#endif
//Point头文件(对外提供的接口):point.h
#ifndef POINT_H
#define POINT_H
extern
const
void
* Point;
/* 使用方法:new (Point, x, y); */
#endif
//Point内部头文件(外面看不到):point.r
#ifndef POINT_R
#define POINT_R
struct
Point
{
const
void
* base;
//继承,基类指针,放在第一个位置,const是防止修改
int
x, y;
//坐标
};
#endif
//Point的C文件:point.c
#include “point.h”
#include “new.h”
#include “point.h”
#include “point.r”
static
void
* Point_ctor (
void
* _self,
va_list
* app)
{
struct
Point * self = _self;
self ->x =
va_arg
(* app,
int
);
self ->y =
va_arg
(* app,
int
);
return
self;
}
static
void
Point_draw (
const
void
* _self)
{
const
struct
Point * self = _self;
printf
(“draw (%d,%d)”, self -> x, self -> y);
}
static
const
struct
Base _Point = {
sizeof
(
struct
Point), Point_ctor, 0, Point_draw
};
const
void
* Point = & _Point;
//测试程序:main.c
#include “point.h”
#include “new.h”
int
main (
int
argc,
char
** argv)
{
void
* p =
new
(Point, 1, 2);
draw(p);
delete
(p);
}
|
同样,Circle要继承Point,则可以这样:
1
2
3
4
5
6
7
8
9
|
struct
Circle
{
const
struct
Point point;
//放在第一位,可表继承
int
radius;
};
|
5、 多态
可以是用C语言中的万能指针void* 实现多态,接上面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//测试main.c
void
* p =
new
(Point, 1, 2);
void
* pp =
new
(Circle, 1, 2);
draw(p);
//draw函数实现了多态
draw(pp);
delete
(p);
delete
(pp);
|
6、 总结
C语言能够模拟实现面向对象语言具有的特性,包括:多态,继承,封装等,现在很多开源软件都了用C语言实现了这几个特性,包括大型开源数据库系统postgreSQL,可移植的C语言面向对象框架GObject,无线二进制运行环境BREW。采用C语言实现多态,继承,封装,能够让软件有更好的可读性,可扩展性。
7、 参考资料
(1) 《C语言中extern和static用法》:
http://www.cnblogs.com/hishope/archive/2008/08/28/1278822.html
(2) 《三、使用GObject——私有成员和静态变量》:
http://blog.csdn.net/wormsun/archive/2009/11/25/4874465.aspx
(3) 《技巧:用 C 语言实现程序的多态性》:
http://www.ibm.com/developerworks/cn/linux/l-cn-cpolym/index.html?ca=drs-
(4) 书籍《Object-Oriented Programming With ANSI-C》
8、 代码下载
本文中的代码可以在此处下载:代码下载
原创文章,转载请注明: 转载自董的博客
本文链接地址: http://dongxicheng.org/cpp/ooc/
C语言下的封装、继承与多态
上次课,钱SIR提到,Liux下面也有很多用C实现的面向对象的结构。比较感觉兴趣,就在网上查了一些资料,原来C语言模拟实现面向对象语言所具有的特性:多态,继承,封装,也是一件很简单的事儿。并且现在很多开源软件都了用C语言实现了这几个特性,包括大型开源数据库系统postgreSQL,可移植的C语言面向对象框架GObject。
在自己机器上实践了下,感叹C语言的灵活与强大!总结一下,以便交流:
一、基础知识
(1)结构体
结构体可以嵌套,因而可以把一个结构体当成另一个结构体的成员,如:
- struct Point{
- int x;
- int y;
- };
- struct Circle {
- struct Point point_;
- int radius;
- };
该结构体与以下定义完全一样(包括内存布置都一样
- struct Circle {
- int x;
- int y;
- int radius;
- };
(2)void *
指针是整个 C 语言的精髓所在。而你也一直敬畏着指针,又爱又恨地使用着它。许多教材都告诉你,int *叫做指向整型的指针,而 char *是指向字符型的指针,等等等等不一而足。然而这里有一个另类的指针家族成员——void *。不要按照通常的命名方式叫它做指向void 类型的指针,它的正式的名字叫做:可以指向任意类型的指针。
(3)C中的参数个数可变函数
可变参数函数的原型声明:
- type VAFunction(type arg1, type arg2, … );
参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用"..."表示。固定参数和可选参数公同构成一个函数的参数列表。
标准C/C++包含头文件stdarg.h,该头文件中定义了操作不定变量的相关宏:
- void va_start ( va_list arg_ptr, prev_param ); /* ANSI version */
- type va_arg ( va_list arg_ptr, type );
- void va_end ( va_list arg_ptr );
在这些宏中,va就是variable argument(可变参数)的意思;
arg_ptr 是指向可变参数表的指针;
prev_param 指可变参数表的前一个固定参数;
type 为可变参数的类型。
va_list 也是一个宏,其定义为typedef char * va_list,实质上是一char型指针。
具体用法可以参考:http://blog.chinaunix.net/space.php?uid=9174178&do=blog&cuid=484548
二、封装
封装的主要含义是隐藏内部的行为和信息,使用者只用看到对外提供的接口和公开的信息。
在C语言中的实现方法:把私有数据信息放在一个不透明的priv变量或者结构体中,只有类的实现代码才知道priv或者结构体的真正定义。
例如:
头文件:
- //========头文件:Point.h文件========
- #ifndef POINT_H
- #define POINT_H
- typedef struct Point point;
- typedef struct pointPrivate pointPrivate;
- <pre class="cpp" name="code">struct Point
- {
- struct pointPrivate *pp;};
- int get_x(point *point_);
- int get_y(point *point_);
- point * new_point(int x,int y);
- }
- #endif
- //=======C文件:Point.c文件========
- #include "Point.h"
- #include<stdlib.h>
- struct pointPrivate;
- int x;
- int y;
- };
- int get_x(point *point_){
- return point_->pp->x;
- }
- int get_y(point *point_){
- return point_->pp->y;
- }
- point* new_point(int x,int y){
- point* p=(point*)malloc(sizeof(point));
- p->pp=(pointPrivate*)malloc(sizeof(pointPrivate));
- p->pp->x=x;
- p->pp->y=y;
- return p;
- }
- int main()
- {
- point* p = new_point(1,2);
- //printf("x:%d,y:%d\n",p->pp->x,p->pp->y);
- printf("x:%d,y:%d\n",get_x(p),get_y(p));
- }
有意思的是:这段代码生成的exe文件可能会被360误认为病毒。
三、继承
在C语言中,可以利用“结构在内存中的布局与结构的声明具有一致的顺序”这一事实实现继承。 比如我们要设计一个作图工具,其中可能涉及到的对象有Point(点),Circle(圆),由于圆是由点组成的,所有可以看成Circle继承自Point。另外,Point和Circle都需要空间申请,空间释放等操作,所有他们有共同的基类Base。
- //基类Base的内部头文件Base.r,对外隐藏
- #ifndef BASE_R
- #define BASE_R
- #include <stdarg h="">
- struct Base {
- size_t size;
- void * (* ctor) (void * self, va_list * app);//构造函数
- void * (* dtor) (void * self); //析构函数
- void (* draw) (const void * self);//作图函数
- };
- #endif
- //Point的内部头文件Point.r,对外隐藏
- #ifndef POINT_R
- #define POINT_R
- struct Point {
- const void * base; //继承Base类,基类指针,放在第一个位置,const是防止修改
- int x, y; //坐标
- };
- #define x(p) (((const struct Point *)(p)) -> x)
- #define y(p) (((const struct Point *)(p)) -> y)
- #endif
- //Point的头文件Point.h(对外提供接口)
- #ifndef POINT_H
- #define POINT_H
- extern const void * Point; /* new(Point, x, y); */
- void move (void * point, int dx, int dy);
- #endif
- //Point的源文件Point.c
- #include <stdio h="">
- #include "Point.h"
- #include "Point.r"
- #include "new.h"
- #include "Base.r"
- /**********Point类自己的构造函数***********/
- static void * Point_ctor (void * _self, va_list * app){
- struct Point * self = _self;
- self -> x = va_arg(* app, int);
- self -> y = va_arg(* app, int);
- return self;
- }
- /**********Point类自己的绘图函数***********/
- static void Point_draw (const void * _self){
- const struct Point * self = _self;
- printf("Point at %d,%d\n", self -> x, self -> y);
- }
- static const struct Base _Point = {
- sizeof(struct Point), Point_ctor, 0, Point_draw
- };
- const void * Point = & _Point;
- void move (void * _self, int dx, int dy){
- struct Point * self = _self;
- self -> x += dx, self -> y += dy;
- }
- //Circle内部头文件Circle.r,对外隐藏
- #ifndef CIRCLE_R
- #define CIRCLE_R
- #include "Point.r"
- struct Circle {
- const struct Point _; //继承Point类,需放在第一位
- int rad;
- };
- #endif
- //Circle的头文件Circle.h(对外提供接口)
- #ifndef CIRCLE_H
- #define CIRCLE_H
- #include "Point.h"
- extern const void * Circle; /* new(Circle, x, y, rad) */
- #endif
- //Circle的源文件Circle.c
- #include <stdio h="">
- #include "Circle.h"
- #include "Circle.r"
- #include "new.h"
- #include "Base.r"
- /**********Circle类自己的构造函数***********/
- static void * Circle_ctor (void * _self, va_list * app){
- struct Circle * self = ((const struct Base *) Point) -> ctor(_self, app);
- self -> rad = va_arg(* app, int);
- return self;
- }
- /**********Circle类自己的绘图函数***********/
- static void Circle_draw (const void * _self){
- const struct Circle * self = _self;
- printf("circle at %d,%d rad %d\n",x(self), y(self), self -> rad);
- }
- static const struct Base _Circle = {
- sizeof(struct Circle), Circle_ctor, 0, Circle_draw
- };
- const void * Circle = & _Circle;
- //内存管理类头文件new.h(对外提供接口)
- #ifndef NEW_H
- #define NEW_H
- void * new (const void * base, ...);
- void delete (void * item);
- void draw (const void * self);
- #endif
- //内存管理类的源文件:new.c
- #include <assert h="">
- #include <stdlib h="">
- #include <stdarg h="">
- #include "Base.r"
- void * new (const void * _class, ...){
- const struct Base * base = _class;
- void * p = calloc(1, base -> size);
- assert(p);
- * (const struct Base **) p = base;
- if (base -> ctor){
- va_list ap;
- va_start(ap, _class);
- p = base -> ctor(p, & ap);
- va_end(ap);
- }
- return p;
- }
- void delete (void * self){
- const struct Base ** cp = self;
- if (self && * cp && (* cp) -> dtor)
- self = (* cp) -> dtor(self);
- free(self);
- }
- void draw (const void * self){
- const struct Base * const * cp = self;
- assert(self && * cp && (* cp) -> draw);
- (* cp) -> draw(self);
- }</stdarg></stdlib></assert></stdio></stdio></stdarg>
- #include "Circle.h"
- #include "new.h"
- int main (int argc, char ** argv)
- {
- void * p;
- int i;
- for(i=0; i<2; i++)
- {
- if(i==0)
- p = new(Circle, 1, 2, 3);
- else
- p = new(Point, 1, 2);
- draw(p);
- move(p, 10, 20);
- draw(p);
- delete(p);
- }
- return 0;
- }
circle at 1,2 rad 3
circle at 11,22 rad 3
Point at 1,2
Point at 11,22
五、总结
面向对象是一种程序设计思想,而 C 语言则是一种编程语言。也许它并不是专门为了面向对象编程而设计,但是这绝不意味着它不能实现面向对象的程序设计。当然以上所展示的这几个操作,如果是用别的编程语言,可能只要寥寥几行就可以完成,但是 C 语言想告诉我们的是:也许我不擅长,但是并不意味着我做不到。
注:
有些头文件名被csdn误认为内部命令,而变很很诡异,请以所下载的代码为准。
转载自http://blog.csdn.net/liql2007/article/details/6866972
________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
- #include <stdio.h>
- #include <conio.h>
- #define MAX 10
- typedef struct Animal{
- void *this;
- char name[MAX];
- void (*shout)(struct Animal *);
- }Animal;
- typedef struct Swimmable{
- void *this;
- void (*swim)(struct Swimmable *);
- }Swimmable;
- typedef struct Dog{
- Animal *Animal;
- Swimmable *Swimmable;
- }Dog;
- typedef struct Cat{
- Animal *Animal;
- Swimmable *Swimmable;
- }Cat;
- void shout_Cat(Animal *cat){
- printf("%s miaomiao.\n", cat->name);
- }
- void swim_Cat(Swimmable *cat){
- printf("%s cannot swim.\n", ((Cat *)(cat->this))->Animal->name);
- }
- void shout_Dog(Animal *dog){
- printf("%s wangwang.\n", dog->name);
- }
- void swim_Dog(Swimmable *dog){
- printf("%s can swim.\n", ((Dog *)(dog->this))->Animal->name);
- }
- void visit_Animal(Animal *animal){
- animal->shout(animal);
- }
- void visit_Swimmable(Swimmable *swimmable){
- swimmable->swim(swimmable);
- }
- void initialize_Dog(Dog* dog, char *name){
- dog->Animal = (Animal *) malloc(sizeof(Animal));
- dog->Animal->this = dog;
- dog->Animal->shout = shout_Dog;
- strcpy(dog->Animal->name, name);
- dog->Swimmable = (Swimmable *) malloc(sizeof(Swimmable));
- dog->Swimmable->this = dog;
- dog->Swimmable->swim = swim_Dog;
- }
- void initialize_Cat(Cat* cat, char *name){
- cat->Animal = (Animal *) malloc(sizeof(Animal));
- cat->Animal->this = cat;
- cat->Animal->shout = shout_Cat;
- strcpy(cat->Animal->name, name);
- cat->Swimmable = (Swimmable *) malloc(sizeof(Swimmable));
- cat->Swimmable->this = cat;
- cat->Swimmable->swim = swim_Cat;
- }
- void finalize_Dog(Dog *dog){
- free(dog->Animal);
- free(dog->Swimmable);
- }
- void finalize_Cat(Cat *cat){
- free(cat->Animal);
- free(cat->Swimmable);
- }
- void main(){
- Dog dog;
- Cat cat;
- clrscr();
- initialize_Dog(&dog, "Wangcai");
- initialize_Cat(&cat, "Kitty");
- visit_Animal(dog.Animal);
- visit_Animal(cat.Animal);
- visit_Swimmable(dog.Swimmable);
- visit_Swimmable(cat.Swimmable);
- finalize_Dog(&dog);
- finalize_Cat(&cat);
- }
所有的面向对象的设计模式也都能类似实现。
虽然没有C++那么方便地面向对象,但是灵活性更强,如可以让多个实例共享一个父实例等。
转载自http://www.iteye.com/topic/699588
________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
【jixingzhong】:
[Ref]
#include <stdio.h>
#include <stdlib.h>
#define VIRTUAL
struct vtable /*虚函数表*/
{
int (*p_geti)(void *); /*虚函数指针*/
void (*p_print)(); /*虚函数指针*/
};
struct shape /*类shape*/
{
void *vptr; /*虚表指针 - 指向vtable*/
int i;
};
struct circle /*类circle*/
{
void *vptr; /*虚表指针 - 指向vtable*/
int i;
};
struct rectangle /*类rectangle*/
{
void *vptr; /*虚表指针 - 指向vtable*/
int i;
};
//------------------------------------
//print() - 虚函数
/*真正调用的函数,在其内部实现调用的多态*/
VIRTUAL void print(void *self) /*参数是对象指针*/
{
const struct vtable * const *cp = self;
(*cp)->p_print();
}
void shape_print()
{
printf("this is a shape!\n");
}
void circle_print()
{
printf("this is a circle!\n");
}
void rectangle_print()
{
printf("this is a rectangle!\n");
}
//------------------------------------------------------
//geti() - 虚函数
VIRTUAL int geti(void *self)
{
const struct vtable * const *cp = self;
return (*cp)->p_geti(self); /*这一行出问题*/
}
int shape_geti(struct shape *self) /*具体函数实现时,参数还要是其类型指针*/
{
return self->i;
}
int circle_geti(struct circle *self) /*具体函数实现时,参数还要是其类型指针*/
{
return self->i;
}
int rectangle_geti(struct rectangle *self) /*具体函数实现时,参数还要是其类型指针*/
{
return self->i;
}
int main(int argc, char *argv[])
{
struct shape _shape; /*shape的对象_shape*/
struct circle _circle; /*circle的对象_circle*/
struct rectangle _rectangle; /*rectangle的对象_rect*/
/*声名虚表*/
struct vtable shape_vtable; /*shape对象的vtable*/
struct vtable circle_vtable; /*circle对象的vtable*/
struct vtable rectangle_vtable; /*rectangle的虚表*/
/*给类分配虚表*/
_shape.vptr = &shape_vtable; /*将虚表挂上*/
_circle.vptr = &circle_vtable; /*将虚表挂上*/
_rectangle.vptr = &rectangle_vtable; /*将虚表挂上*/
/*给虚表对应相应的函数*/
shape_vtable.p_print = shape_print; /*赋值相应的函数*/
circle_vtable.p_print = circle_print; /*赋值相应的函数*/
rectangle_vtable.p_print = rectangle_print; /*赋值相应的函数*/
/*给虚表对应相应的函数*/
shape_vtable.p_geti = shape_geti;
circle_vtable.p_geti = circle_geti;
rectangle_vtable.p_geti = rectangle_geti;
/*动态联编实现多态*/
/*因类型的不同而作出不同的反映*/
print(&_shape);
print(&_circle);
print(&_rectangle);
_shape.i = 5;
_circle.i = 19;
_rectangle.i = 1;
/*动态联编实现多态*/
/*因类型的不同而作出不同的反映*/
printf("_shape's i is : %d\n", geti(&_shape));
printf("_circle's i is : %d\n", geti(&_circle));
printf("_rectangle's i is : %d\n", geti(&_rectangle));
system("PAUSE");
return 0;
}
// From http://www.programfan.com/club/showpost.asp?id=25794
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
另外还有一本关于这方面的英文的书听说蛮好的 《Object-Oriented Programming With ANSI-C》 新浪爱问共享资料有免费下载http://ishare.iask.sina.com.cn/f/10797511.html