《设计模式学习及其C语言实现笔记.doc》由会员分享,可在线阅读,更多相关《设计模式学习及其C语言实现笔记.doc(11页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、【精品文档】如有侵权,请联系网站删除,仅供学习与交流设计模式学习及其C语言实现笔记.精品文档.设计模式学习及其C语言实现笔记第1章:面向对象C语言(Object Oriented C)我曾经在嵌入式控制系统工作过,苦于嵌入式系统编程一直是C语言,而没法用C+或其他高级语言的面向对象方法编程。每次做项目,代码基本上都是重头再来,以前的代码有少量的可以copy过来直接用,开发和维护很不方便。偶然间发现UML+OOPC嵌入式C语言开发精讲里面详细的讲述了OOC的原理和实现方法,感觉不错。遗憾的是上面只提供接口和多态的实现方法,没有提供继承的处理。根据本人的研究,将其上面的宏文件进行修改和扩充,提出一
2、种可行而且结构明了的继承实现方法。至此,C的OO编程中的封装、继承、多态都全实现了。面向对象C语言(Object Oriented C)只是运用单纯的C的宏(Macro)技巧,实现面向对象的基本技术。借用OOC.H 文件的宏,就可以实现类的封装、继承、多态。OOC毕竟不是一门语言,不可能做出与C+或C#这些面向对象语言一样的干练、简洁,有的甚至没法实现比如对private成员变量的限制等。但OOC方法借用宏在一定的规则下还是能做出比较漂亮的面向对象代码。下面是对比C+介绍OOC.H文件中的对象的命名、定义、封装等的规则。1、类的封装和对象的构造1.1类的封装(类的定义)在C+中,类的定义如下c
3、lass myClassprivate:int a; /-类属性(变量)public: myClass(); void Set(int a); void put();在OOC中“类”实际是结构体,myClass 定义的风格为CLASS(myClass)int a; /-类属性(变量)void (*init)(void *t); /-初始化成员变量用函数void (*Set)(void *t,int a); /-函数指针void (*Put)(void *t); 在C+和OOC的类封装中,最大的区别是OOC用函数指针来表示类的方法(或成员函数);此外OOC中没有private的变量,privat
4、e变量只能通过定义方法来实现,比如把int a 改写为int _a,这样也许外面误访问的可能性大大降低,但还是不能阻止外面访问。1.2类的成员函数的实现C+中可以在类内部定义成员函数的实现,也可以在类的外面。而OOC中的类实质是struct,其所存放的只能是变量,其中的函数指针变量是用来实现类的方法。因此在OOC的类中是不能定义函数的实现的。OOC中函数也不像C+中的函数属于哪个类就和哪个类紧密结合在一起。OOC中的函数的实现就是普通的函数实现,只是函数的参数要和CLASS()宏定义的函数(指针)的参数变量保持一致。例如myClass类的Set方法可以取名Set,也可以用别的名。如果在一个.C
5、文件中,成员函数取名模式为: funName_ClassName(),这样比较便于阅读和区分。按照该规则,则myClass的方法实现定义如下:void init_myClass(void *t)myClass *cthis = t;cthis-a = init_Number; /赋你想要赋给的初始值,也可以通过增加init()参数传进来void Set_myClass(void*t,int a)myClass *cthis = t;cthis-a = a;void Put _myClass(void*t)myClass *cthis = t;printf(“n this is myClass
6、fun”);这里和C+不同的是,在C+中,成员函数可以直接访问类的成员变量,而OOC中的成员函数要访问成员变量,必须要有参数传递过来,因此,OOC中凡是要访问其成员变量的必须要增加一个void*t 的参数。比如函数Set()在C+只要有一个int参数就行了,在OOC中必须要带有一个void*指针参数,通过该参数才能访问到myClass类的其他变量或者成员函数。1.3对象的构造在C+中,定义了一个对象的同时也调用了构造函数。在OOC中没有这样的功能,OOC的对象定义,实际上只是定义了个含有函数指针的结构体,要使得该结构体变成相当于C+中的对象,必须要对该对象 (结构体)的成员变量进行初始化和方法
7、(指针函数)与具体定义的函数进行关联。在C+中这两步一下就在定义对象时候自动调用构造函数现了,而OOC中只能分两步走。因此,在myClass定义中,C+没有 void init(void *t) 函数,而任何OOC定义的类必须要包含该函数(指针,接口和抽象类除外),否则无法对成员变量进行初始化。根据OOC中的宏,以myClass为例,对象的构造函数格式如下:CTOR(myClass)FUNCTION_SETTING(init, init_myClass); FUNCTION_SETTING(Set, Set_myClass);FUNCTION_SETTING(Put, Put_myClass)
8、;END_CTOR构造函数把对象中的函数指针和函数关联起来,调用函数指针就是对函数的调用。通过该构造函数后,OOC定义的对象才是一个真正意义上的对象,即函数指针有了函数的功能。1.4、对象的实例化和规则定义好类后就要实例化,在C+中,myClass的对象定义为:myClass myObject;其中已经隐含了myObject对象的构造和成员变量的初始化。 在OOC中,其定义和处理3步如下:myClass myObject; / 1、定义对象CLASS_CTOR(myClass,myObject); / 2、构造对象使得函数指针和函数关联myObject.init(&myObject); / 3
9、、初始化对象的成员变量,注意:& myObject(取地址)至此,当个类的定义、封装、对象的构造和初始化就结束了。2、继承和接口的实现2.1、继承和C实现在C+继承就是子类不需要重新定义拥有了其父类的public 和protect 的成员变量和成员函数。如果子类中重新定义了父类的方法,如果父类该方法是虚函数,则子类函数覆盖了父类函数;如果父类函数不是虚函数则为子类函数隐藏了父类函数。覆盖和隐藏的区别是:如果发生覆盖,当父类指针指向子类对象时,通过指针调用同名虚函数,则执行子类的虚函数;如果发生隐藏,则调用的是父类的同名函数。例如:class BaseApublic: BaseA() Virtu
10、al void Put()cout “ 调用BaseA的函数”endl;class BaseBpublic: BaseB() void Put()cout “ 调用BaseB的函数” endl;class ChildA : public BaseApublic: ChildA () void Put()cout “ 调用ChildA的函数” endl;class ChildB : public BaseBpublic: ChildB () void Put()cout “ 调用ChildB的函数” Put(); /- 调用子类ChildA的Put()函数; BaseB *pb = new Ch
11、ildB(); pb-Put(); /- 调用父类BaseB的Put()函数;输出结果是:调用ChildA的函数调用BaseB的函数在OOC 中只是借用C 的宏没法实现C+这些复杂的机制,但能实现基本的继承,即拥有父类的全部属性和方法。OOC中没有私有、保护属性的概念,所有父类属性和方法都是可见的。通过构造函数的处理,可以在OOC中实现隐藏和覆盖。下面是OOC中的隐藏和覆盖的讲解,在设计模式中更多的是覆盖,而隐藏用到的比较少。2.2、继承中覆盖和隐藏的实现为了方便解说,用myClass做基类,则定义子类myChildA的方法如下CLASS(myChildA) /-本书OOC目前只研究单继承EX
12、TENDS(myClass); /-表示继承myClass类,必须放在最前面 int data;void (*init)(void *t);void (*Put)(void*t);子类要注意的地方有3点:1、 被继承的类的宏说明EXTENDS(baseClass) 必须放在子类定义的最开头。2、 子类的init()函数里面必须要调用基类的init()函数,否则基类的属性没有初始化。3、 在构造子类的地方CTOR(myChildA)下最开头必须要增加调用基类构造的宏。myChildA 类的初始化函数定义如下:void init_ myChildA(void *t)myChildA *cthis
13、= t;cthis- myClass.init(&cthis- myClass); /-基类的初始化, 注意:&cthis- myClass/-与基类的init()函数参数要一致cthis-data = yourNumber; myChildA虚函数覆盖的实现假定myClass类中Put()为虚函数,则子类必须定义自己Put()的实现,myChildA自己重新定义的Put()如下:void Put_ myChildA ()printf(“this is myChildA fun、n”);于是在构造函数中,遵循C+的原理,先对基类进行构造,然后对基类的虚函数重新绑定,具体处理代码如下CTOR(m
14、yChildA)BASE_CTOR(myClass); /-调用基类构造函数实现对基类函数关联FUNCTION_SETTING(myClass.Put, Put_ myChildA); / -Put()的虚函数实现关联绑定FUNCTION_SETTING(init, init_ myChildA); FUNCTION_SETTING(Put, init_ myChildA);END_CTOR在CTOR(myChildA)中,通过重新设置子类内的myChildA .Put的关联函数,从而实现虚函数的意义。如果要实现C+中的隐藏功能,只要将 FUNCTION_SETTING(myChildA .P
15、ut, Put_ myChildA)去掉就是了,因为BASE_CTOR(myClass)已经将myClass.Put与Put_ myClass 关联了。实现隐藏的构造函数如下:CTOR(myChildA)BASE_CTOR(myClass); /-调用基类构造函数实现对基类函数关联FUNCTION_SETTING(init, init_ myChildA); FUNCTION_SETTING(Put, init_ myChildA);END_CTOR继承中的覆盖和隐藏中,都共同的调用宏BASE_CTOR(baseClass),调用该宏的前提条件是baseClass不是抽象类,baseClass
16、必须有自己的 CTOR(baseClass), 否则编译可能正确但连接运行一定错误。2.3、接口和抽象类及其多态的实现接口是Java 和C#提出的概念,在C+中抽象类可以当作接口处理。同样,OOC中定义的接口和抽象类的差别的是:接口没有数据成员变量,接口没有自己的构造函数;抽象类可以有数据成员变量。本书OOC中的接口就是没有数据成员的抽象类。抽象类如果带有数据成员则必须有初始化成员函数和构造函数。下面结合一个简单运算器的实例说明OOC中的接口和多态的实现。简单的运算器有加法、减法、乘法、除法的功能。多态就是用相同的语句结构表达不同的功能。采用多态处理计算器的目的,就是客户调用端运算的代码是一致
17、的,只需要少量的变化就能实现不同的功能。基于这样的分析,首先要抽象一个类或者接口,它负责不同情况下的计算,所以它没有具体的代码。具体的实现代码由其子类来实现,于是OOC里定义该接口为:INTERFACE(IOperation)double GetResult(double numA, double numB);这就是OOC中定义接口的方法。本例中的接口只有一个函数,根据需要可以增加。接下来巴加法、减法、乘法、除法分别定义成IOperation接口的实现(也可以说是子类)。/-加法类CLASS(COperationAdd)IMPLEMENTS(IOperation);/-减法类CLASS(COp
18、erationSub)IMPLEMENTS(IOperation);乘法类、和除法类与此类同,在此省略。OOC中用IMPLEMENTS()宏主要是为了更加接近Java的表达,实际上用 EXTENDS() 也是一样,定义含义完全相同的两个宏主要是方便理解。对于接口的实现(或者说继承接口的子类),它的构造和继承有点不同,最大的不同是在CTOR()下面不需要调用父类构造函数的宏BASE_CTOR()。接口方法的关联与继承中的虚函数的关联比较类似。下面是COperationAdd类的构造函数:CTOR(COperationAdd)FUNCTION_SETTING (IOperation.GetResu
19、lt, GetResult_COperationAdd); /-接口函数的实现关联END_CTOR减法类的构造函数:CTOR(COperationSub)FUNCTION_SETTING (IOperation.GetResult, GetResult_COperationSub); /-接口函数设置END_CTOR其他类的关联也是类似,只是关联的实现函数名不同而已。主函数的调用void main() /-定义运算操作对象IOperation *pOper; /-定义接口指针,多态实现用COperationAdd Add;COperationSub Sub;COperationMul Mul;
20、COperationDiv Div;double result=0;/-类的构造CLASS_CTOR(COperationAdd,Add);CLASS_CTOR(COperationSub,Sub);CLASS_CTOR(COperationMul,Mul);CLASS_CTOR(COperationDiv,Div);/-静态内存分配处理方法pOper = &Add;result = pOper-GetResult(53.12,86.4);printf(ADD: 53.12+86.4 =%6.3frn,result);pOper = ⋐ /-减法result = pOper-GetRe
21、sult(53.12,86.4);printf(Sub: 53.12-86.4 =%6.3frn,result);pOper = &Mul; /-乘法result = pOper-GetResult(53.12,86.4);printf(Mul: 53.12*86.4 =%6.3frn,result);pOper = &Div; /-除法result = pOper-GetResult(53.12,86.4);printf(Div: 53.12/86.4 =%6.4frn,result);/-动态内存分配方法-/-加法printf(rn dynamic memory:rnn);pOper =N
22、ew(COperationAdd);result = pOper-GetResult(12.5,36.8);printf(ADD: 12.5+36.8 =%6.3frn,result);/-减法pOper =New(COperationSub);result = pOper-GetResult(12.5,36.8);printf(Sub: 12.5-36.8 =%6.3frn,result);/-乘法pOper =New(COperationMul);result = pOper-GetResult(12.5,36.8);printf(Mul: 12.5*36.8 =%6.3frn,resul
23、t);/-除法pOper =New(COperationDiv);result = pOper-GetResult(12.5,36.8);printf(Div: 12.5/36.8 =%6.3frn,result);结果输出如下:ADD: 53.12+86.4 =139.520Sub: 53.12-86.4 =-33.280Mul: 53.12*86.4 =4589.568Div: 53.12/86.4 =0.6148 dynamic memory:ADD: 12.5+36.8 =49.300Sub: 12.5-36.8 =-24.300Mul: 12.5*36.8 =460.000Div:
24、12.5/36.8 = 0.340从main()函数的调用可以看出,处理运算始终是 pOper-GetResult(numA , numB) 这条语句,改变其运算功能,只需要改变 pOper 所指向的对象。这就是多态,相同的语句实现不同的功能。抽象和多态在面向对象C和设计模式中将显示出强大的威力。第2章:面向对象C 必要基础知识为了帮助大家理解OOC和更好的使用OOC,这章主要是讲解一些必要的C语言和宏定义,介绍OOC.h 为什么要这样设计。1、 本书的OOC书写规则1.1、 普通类的定义CLASS(className) typeName data; void (*init)(void*t,
25、type others); /必须有该函数,参数至少有void*t typeName (*m_function)(void*t, type others); /-至少有void*t 参数,其他看情况普通类是没有继承过任何类、任何接口的类。普通类必须要有1.2、抽象类的定义ABSTRACT_CLASS(className) typeName data; void (*init)(void*t, type others); /必须有该函数,参数至少有void*t typeName (*m_function)(void*t, type others); /-至少有void*t 参数,其他看情况 实质
26、上和普通类是一样的定义,只是没有自己的方法实现,宏ABSTRACT_CLASS目的是帮助程序的可读性。如果抽象类没有数据data,最好就定义为接口。1.3、接口和实现INTERFACE(IName)typeName (*m_function)( type others); /-参数看情况而定,不一定有void*t参数CLASS(Chid_Implement)IMPLEMENTS(IName);void (*init)(void*t); /-其他方法据情况增减接口的出现就是希望一个接口能有多个实现,一个接口至少有一个实现,接口只有在有2个实现以上才有意义,否则接口可以合并到子类中去。1.4 继承
27、:在子类的最开头加宏EXTENDS(baseClass); 例如CLASS(B) EXTENDS(A); /-B继承A,必须放在最开头1.5、 类的构造: 将函数指针和函数关联没有父类的类的构造:CTOR(ClassName)FUNCTION_BINDING(FUN1,FUN2);END_CTOR有继承的类的构造:CTOR(ClassName)BASE_CTOR(baseClass)FUNCTION_BINDING(FUN1,FUN2);END_CTOR1.6、 动态内存分配ClassName *pClass = New_ClassName(); 或者ClassName *pClass = N
28、EW(ClassName); 或ClassName *pClass = New(ClassName);提供多种方法,读者可以根据自己的喜好选择,本人习惯用pClass = New(ClassName),方式,这样New()看起来更接近C+运算符new。1.7、 显式构造函数CLASS_CTOR(ClassName, objName); 或者ClassName_Setting(&objName)这两个宏展开后实际上是一样的,提供两种方法是方便不同人的使用习惯。显式构造函数主要用于类实例化后对其进行构造。在动态内存分配中已经隐含了对该宏对应的函数的调用。1.8、 动态对象内存释放DEL(p);或者
29、DELETE(p);2、面向对象OOC.H 的设计及其关键宏含义面向对象C的关键宏头文件OOC.H最初的设计思想来源于高焕堂老师著的UML+OOPC嵌入式C语言开发精讲中的lw_oopc_kc.h。原作中提供了类的封装和接口,以及在这基础上演化的对象、信息传递和接口多态等。本文在其基础上进行了符合自己编程习惯的改进,并增加了继承机制。从而OOC中实现类的封装、继承和多态,从而实现了OO中的三大主要特征。基于此的基础上还考虑虚函数的处理方法等。OOC.H的文件内容为:/*- ooc.h -*/ #ifndef _OOC_H#define _OOC_H#include 默认可以动态内存分配,如果不
30、使用动态内存分配,在 #include OOC.H 前定义#define OOC_SMALL,或者 #define OOC_STATIC#ifndef OOC_SMALL #ifndef OOC_STATIC #define New(type) New_#type() /dynmic mode #define NEW(type) New_#type() /dynmic mode #define DELETE(type) free(type) / free the memory #define DEL(type) free(type) #endif#endif#define CLASS(type
31、) typedef struct type type; struct type#ifndef OOC_SMALL #ifndef OOC_STATIC #ifndef OOC_DYNAMIC #define CTOR(type) void* type#_Setting(type*); void* New_#type() struct type *cthis; cthis = (struct type *)malloc(sizeof(struct type); return type#_Setting(cthis); void* type#_Setting(type *cthis) #else
32、#define CTOR(type) void* New_#type() struct type *cthis; cthis = (struct type *)malloc(sizeof(struct type); #endif #else #define CTOR(type) void* type#_Setting(type *cthis) #endif#endif#define END_CTOR return (void*)cthis; #define FUNCTION_SET(pfun, fun) cthis-pfun = fun;#define IMPLEMENTS(type) typ
33、e type#define INTERFACE(type) typedef struct type type; struct type/-转义定义-/#define CLASS_CTOR(type,obj) type#_Setting(&obj)#define EXTENDS(BaseClass) IMPLEMENTS(BaseClass)#define BASE_CTOR(type)CLASS_CTOR(type,cthis-type); /CLASS_CTOR(A,cthis-A);#define ABSTRACT_CLASS CLASS#define FUNCTION_SETTING F
34、UNCTION_SET/ Virtual 是一个空格,在这里只是提示用,没有任何意义#define Virtual #endif/*- end -*/2.1、宏CLASS(className)CLASS(className)展开后为:typedef struct className className; struct className也就是说:className 不仅是个结构体名,同时还表示:struct className;因为在C中定义结构体变量时候必须有struct在前,如:struct A用结构体A定义一个结构体变量a,应写为:struct A a;这点和C+结构体定义不同。C+的结
35、构体实际就是一个特殊的类,在C+中变量a的定义为:A a (没有带struct)。为了让宏CLASS()定义的“类”(结构体)比较接近C+中定义的类,把struct 通过typedef 方法让 className 隐式包含了struct。2.2、宏CTOR(type) ,在有动态内存分配的情况下, 其定义为: #define CTOR(type) void* type#_Setting(type*); void* New_#type() struct type *cthis; cthis = (struct type *)malloc(sizeof(struct type); return t
36、ype#_Setting(cthis); void* type#_Setting(type *cthis) 也就是CTOR(type)代表了“void* type#_Setting(type*); void* New_#type() ” 这段代码,其中已经包含了一个动态内存分配函数和半个 type#_Setting(type *cthis);结合CTOR(type)FUNCTION_SETTING (f1, f2);END_CTOR就可以知道,CTOR() 和 END_CTOR 之间包含了两个完整的函数,一个动态内存分配函数New_type() 和构造函数 type_Setting().在没有
37、动态内存分配的情况,则表示 “void* type#_Setting(type*)”,即半个函数,组FUNCTION_SETTING (f1, f2); 和 END_CTOR 组合成一个完整的函数,也就是实现对象与其方法函数实体的关联。2.3、宏New(type)这是动态对象分配函数,宏定义为:#define New(type) New_#type()其中#表示连接字符的符号。比如New(classA),先是被替换为New_#classA(), 因为#是连接符号,故变成 New_classA(). 即为宏定义的动态内存分配函数和构造函数,实现了定义的同时也构造了(这很接近C+的模式了)。2.4
38、、宏CLASS_CTOR(type,obj)定义:CLASS_CTOR(type,obj) type#_Setting(&obj)其实是 void* type#_Setting(type*) 宏函数的另一种写法,定义该宏主要是把宏和类从一个字符串中分离出来便于书写和理解。CLASS_CTOR(类名, 对象名)必须要遵守这个规则,否则会出错。2.5、宏BASE_CTOR(base)定义:BASE_CTOR(base)CLASS_CTOR(base, cthis- base);该宏是在派生类的构造函数内用,不能在其他地方使用,也不能独立使用。因为该宏包含有cthis指针。该指针只有在CTOR()
39、. END_CTOR 之间才能引用,cthis 是CTOR()宏内部定义的指针。2.6 宏INTERFACE( type ) 和 宏 IMPLEMENTS(type)定义:#define INTERFACE(type) typedef struct type type; struct type和CLASS()宏的含义一样,单独定义主要是强调其定义的是接口。接口对应的实现的宏是IMPLEMENTS(type),其定义很简单,就是:#define IMPLEMENTS(type) type type也就是定义一个结构体变量,这个结构体类型是type,这个结构体变量名也是type。第3章:活字印刷简
40、单工厂模式在印刷术发明前,人们的印刷书是一个很艰巨的工程,先是请雕刻师将每一页的文字刻在木板上,然后再一页一页的印刷。如果在雕刻过程中,错了一个字或者要改动一个字,该页面的木板又得重新雕刻。印刷结束后,这些雕刻的木板也就没用扔掉,前面雕刻的工作全部废弃。新的一本书的印刷前,这些雕刻工作又得从头再来 今天看起来觉得古人好笨,汉字常用的就是那么几千个,一个个的独立雕刻也不费多少功夫,而雕刻一本本书,动辄少就几万字,多则几十万甚至百万字。一个活字印刷就那么难想到吗?想想我们的软件开发,很多时候,我们何尝不是“雕刻”(编码)一整个软件,编译然后“印刷”(拷贝)给各个用户呢。当用户需求改变时,我们又得重
41、新“雕刻”(重新修改代码),然后再编译“印刷”给用户。想想自己嘲笑古人的笨,回过头来看看自己做的软件开发何尝不是很笨呢?幸运的是后来毕昇发明了活字印刷,每个汉字的独立雕刻。印刷时候把用到的字组合成文章,只要雕刻一次,以后多次重复使用,如果文章修改,就只要更换修改部分的印刷字,个别生僻字没有雕刻立刻雕刻也不费多大的功夫。那么我们的软件开发里是否有“活字印刷呢”?那就是简单工厂模式。下面以一个MP3播放器实例来说明简单工厂模式。3.1 传统过程化设计思想按照传统过程化结构化思想,设计出的C+代码如下class MediaPlayerpublic:void play(string audioType)if (audioType = MP3)playMP3();if (audioType = Wav)playWav();MediaPlayer()private:void playMP3()cout播放 MP3文件endl;void playWav()cout播放 Wav 文件endl;在客户端的代码为:void main()MediaPlayer player;/-播放MP3player.play(MP3);/-播放 Wavplayer.play(Wav);
限制150内