序
相信很多做嵌入式的朋友,做过一些项目后,会有一个瓶颈期,就是明明熟悉了C语言的所有语法,但拿到一个项目不知道如何下手,或者明明知道一个项目该怎么做,去设计代码时,却总会遇到一些逻辑上自相矛盾的地方。这时想要提升自己的代码能力,却又不知道学什么,心想C语言不就那么点东西吗?
如题:什么是好的嵌入式C代码? 其实语言只是一种工具,重要的是思想!在本文中,我将从代码结构和代码逻辑上来阐述。代码结构上将阐述分层设计的要点;逻辑上将从
状态机、查表法、信号槽
、
设计模式(23种)
、
面向对象(封装、继承、多态)
、
防御性编程
等思想,来解决我们经常遇到的逻辑冲突、减少重复代码、增加代码的观赏性和可读性。
如果当你听过很多大道理,依然写不好C代码,就来读读这篇文章吧!!!
【C语言】如何优雅地进行嵌入式C开发?(万字总结)
一、照妖镜:分层设计
分层设计是代码的
照妖镜
,我们通常可以通过它一眼看出一份代码的好坏。
嵌入式程序主要分为:物理层、功能接口层、应用层。
先看一个典型的分层设计模型:
- PHY(物理层) - UART- ADC- GPIO- ……
- USER(功能接口层) - MODBUS- TEMP- LED- ……
- APP(应用层) - CTRL- ……
通常一个项目涉及多人开发,每人负责不同层级。层级之间面临着前期接口的不透明和频繁更改,合并代码时难免存在冲突;代码合并后,在底层更改了接口名,必须人为地通知上层去调用,随着接口增多,合并代码的压力也越大。那么我们如何无视这些未知的冲突,进行快乐的编程呢?也就是说我们可以根本不关心对方接口的名字和实现方式,我们只需要在前期约定格式(对于一个函数接口,所谓的格式即:参数和返回值),各自去实现和调用即可。
1、使用 Static 关键字
来将函数和变量限制在本文件内,避免
命名冲突
2、使用回调函数
①、回调函数的作用
- 在.c文件之间优雅的传递数据,而不用全局变量共享数据 拿STM32为例,像外部中断信号、串口、ADC、按键等输入信号,他们的输入时间节点是未知的,我们不想轮询,芯片 模组厂商一般会采用中断来通知应用层,我们只需要在应用层编写回调函数(底层调用了一个没有实现的和回调函数同类 型的处理函数A),然后调用底层的注册函数把回调函数注册到底层(底层的注册函数就是把回调函数指针作为入参,A就可以指向我们在应用层编写的回调函数了),比如我们熟知的
串口中断回调函数
。 - 封装代码的利器 在上一条中可以看出:我们不需要告诉底层处理函数的名字,然后让底层调用我们去处理数据;也不需要知道底层函数的名字,然后调用它获取数据。我们只要在应用层编写数据处理回调函数,然后把函数指针注册到底层(因为使用了指针,回调函数的名字可以随便取,但入参和返回值类型的格式要符合要求,否则没法和底层关联),底层数据到来时就会自动处理,这之间没有对底层做任何操作,成功解耦。
②、回调函数的应用
我们在编写驱动时,可以模仿这些芯片厂商,比如编写一个ADC驱动:
ADC.h
#ifndef_APP_ADC_H_#define_APP_ADC_H_typedefenum{
ADC1,
ADC2,}ADC_ID_TYPEDEF;typedefvoid(*adcGatherCallBack)(ADC_ID_TYPEDEF adc,uint32_t value);voidadc_callback_register(adcGatherCallBack adcCB);#endif
注意:函数指针和注册函数都是在底层编写
ADC.c
static adcGatherCallBack adcGatherCB;voidadc_callback_register(adcGatherCallBack adcCB){
adcGatherCB = adcCB;}voidadc_task(void){while(1){uint32_t adc_reading =0;for(int i =0; i < NO_OF_SAMPLES; i++){
adc_reading +=adc1_get_raw((adc1_channel_t)channel);}
adc_reading /= NO_OF_SAMPLES;uint32_t voltage =esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
adc_channel = ADC1;adcGatherCB(adc_channel,voltage);//底层当成已有函数使用,在应用层可灵活定义回调函数实现具体操作vTaskDelay(pdMS_TO_TICKS(1000));}}
注意:
adcGatherCB(adc_channel,voltage);
是一个
adcGatherCallBack
类型的指针,不需要去实现,只需要将ADC通道和值传入,待应用层将回调函数注册进来,这个指针就指向回调函数,实现回调函数的实现。
APP.c(应用层)
/*ADC回调函数*/staticvoidadcCallBack(ADC_ID_TYPEDEF adc_channel,uint32_t value){//电压值处理switch(adc_channel){case ADC1://显示电池电压printf("Channel: ADC%d\tVoltage: %dmV\n", adc_channel, value);break;case ADC2://显示VBUS电压break;default:break;}}/*注册所有回调*/staticvoidapp_callback_register(){adc_callback_register(adcCallBack);}/*主逻辑初始化*/voidapp_init(void){app_callback_register();}
二、三板斧:常用机制
学会这三板斧,已经能解决很多常见问题,当大佬看到你代码时,会想:嗯,这小伙子还有点东西,但不多,是个可塑之才。
1、查表法
typedefstruct{char*help_list;
uint8 permission;//操作权限。0:支持读写 1:只读
gpio_port gpio_port_idx;
uint8 pin_index;
Ifx_P *port;
uint8 default_state;}GPIO_LIST;
GPIO_LIST gpio_listor[]={// help_list permission port_idx pin Ifx_P * default_state{"MCU_LED_EN(RW)\t", GPIO_RW, port13,0,&MODULE_P13 ,0},//MCU_RUN_LED{"MCU_CAN0_STB(RW)", GPIO_RW, port01,4,&MODULE_P01 ,0},//CAN0_STB{"MCU_CAN1_STB(RW)", GPIO_RW, port01,6,&MODULE_P01 ,0},//CAN1_STB{"MCU_CAN2_STB(RW)", GPIO_RW, port01,7,&MODULE_P01 ,0},//CAN2_STB{"MCU_CAN3_STB(RW)", GPIO_RW, port00,9,&MODULE_P00 ,0},//CAN3_STB{"MCU_CAN4_STB(RW)", GPIO_RW, port34,3,&MODULE_P34 ,0},//CAN4_STB{"MCU_ETH_EN(RW)\t", GPIO_RW, port12,0,&MODULE_P12 ,0},//ETH_PHY_EN{"MCU_PHY_RST(RW)\t", GPIO_RW, port11,14,&MODULE_P11 ,0},//ETH_PHY_RST{"J2A_9296_EN1(RW)", GPIO_RW, port22,7,&MODULE_P22 ,1},//CAM_EN1{"J2A_9296_EN2(RW)", GPIO_RW, port22,8,&MODULE_P22 ,1},//CAM_EN2{"J2A_9296_EN3(RW)", GPIO_RW, port22,9,&MODULE_P22 ,1},//CAM_EN3{"CAME_OFF(RW)\t", GPIO_RW, port33,2,&MODULE_P33 ,0},//CAM_POW};#defineGPIO_MAX_NUMsizeof(gpio_listor)/sizeof(GPIO_LIST)voidinit_gpio(void){for(uint8 i =0; i < GPIO_MAX_NUM; i++){if(gpio_listor[i].permission == GPIO_RW){IfxPort_setPinMode(gpio_listor[i].port, gpio_listor[i].pin_index, IfxPort_Mode_outputPushPullGeneral);IfxPort_setPinState(gpio_listor[i].port, gpio_listor[i].pin_index, gpio_listor[i].default_state);}}}
2、状态机
不是一看到switch/case就是状态机,要有状态的切换。
假如有如下状态关系:
代码参考网上:
//定义枚举类型STATE_t表示状态机状态:typedefenum{
STATE1 =0,
STATE2,
STATE3,
STATE4,}STATE_t;//定义ACTION_MAP_t结构体类型,表示状态机状态属性:typedefvoid(*STATE_ACTION)(void);typedefstructACTION_MAP{
STATE_t stStateID;
STATE_ACTION EnterAct;
STATE_ACTION RunningAct;
STATE_ACTION ExitAct;}ACTION_MAP_t;//建立“动作”表:voidstate1_entry(void){printf("state1_entry\n");}voidstate1_do(void){printf("state1_do\n");}voidstate1_exit(void){printf("state1_exit\n");}voidstate2_entry(void){printf("state2_entry\n");}voidstate2_do(void){printf("state2_do\n");}voidstate2_exit(void){printf("state1_exit\n");}voidstate3_entry(void){printf("state3_entry\n");}voidstate3_do(void){printf("state3_do\n");}voidstate3_exit(void){printf("state3_exit\n");}voidstate4_entry(void){printf("state4_entry\n");}voidstate4_do(void){printf("state4_do\n");}voidstate4_exit(void){printf("state4_exit\n");}
ACTION_MAP_t actionMap[]={{STATE1, state1_entry, state1_do, state1_exit},{STATE2, state2_entry, state2_do, state2_exit},{STATE3, state3_entry, state3_do, state3_exit},{STATE4, state4_entry, state4_do, state4_exit},};//定义枚举类型EVENT_t表示事件:typedefenum{
EVENT1 =0,
EVENT2,
EVENT3,
EVENT4,
EVENT5,
EVENT_MAP_END
}EVENT_t;//定义EVENT_MAP_t结构体类型,表示事件表属性:typedefstructEVENT_MAP{
EVENT_t stEventID;
STATE_t stCurState;
STATE_t stNextState;}EVENT_MAP_t;//根据状态机流程图建立事件表:
EVENT_MAP_t eventMap[]={{EVENT1, STATE1, STATE2},{EVENT2, STATE2, STATE3},{EVENT3, STATE3, STATE4},{EVENT4, STATE4, STATE1},{EVENT5, STATE1, STATE4},{EVENT_MAP_END,0,0},};//定义状态机结构体类型:typedefstructFSM{
STATE_t stCurState;
STATE_t stNextState;
ACTION_MAP_t *pActionMap;
EVENT_MAP_t *pEventMap;}FSM_t;//定义状态机注册函数:voidfsm_init(FSM_t* pFsm,EVENT_MAP_t* pEventMap,ACTION_MAP_t *pActionMap){
pFsm->stCurState =0;
pFsm->stNextState = EVENT_MAP_END;
pFsm->pEventMap = pEventMap;
pFsm->pActionMap = pActionMap;}//定义状态机转换函数:voidfsm_state_transfer(FSM_t* pFsm, EVENT_t stEventID){uint8_t i =0;for(i=0; pFsm->pEventMap[i].stEventID<EVENT_MAP_END; i++){if((stEventID == pFsm->pEventMap[i].stEventID)&&(pFsm->stCurState == pFsm->pEventMap[i].stCurState)){
pFsm->stNextState = pFsm->pEventMap[i].stNextState;return;}}}//定义动作执行函数:voidaction_perfrom(FSM_t* pFsm){if(EVENT_MAP_END != pFsm->stNextState){
pFsm->pActionMap[pFsm->stCurState].ExitAct();
pFsm->pActionMap[pFsm->stNextState].EnterAct();
pFsm->stCurState = pFsm->stNextState;
pFsm->stNextState = EVENT_MAP_END;}else{
pFsm->pActionMap[pFsm->stCurState].RunningAct();}}//测试intmain(void){int i =0;
FSM_t stFsmWeather;//定义状态机fsm_init(&stFsmWeather,eventMap,actionMap);//注册状态机while(1){usleep(10);printf("i = %d\n",i++);action_perfrom(&stFsmWeather);//利用i产生EVENT1~EVENT5if(0==(i%11)){fsm_state_transfer(&stFsmWeather,EVENT1);}if(0==(i%31)){fsm_state_transfer(&stFsmWeather,EVENT2);}if(0==(i%74)){fsm_state_transfer(&stFsmWeather,EVENT3);}if(0==(i%13)){fsm_state_transfer(&stFsmWeather,EVENT4);}if(0==(i%19)){fsm_state_transfer(&stFsmWeather,EVENT5);}}return0;}
3、信号槽
信号槽的概念来自QT,我们可以用C语言模拟信号槽的机制。
代码参考网上:
#ifndef_SIMPLE_SIGNAL_SOLTS_H_#define_SIMPLE_SIGNAL_SOLTS_H_#include"string.h"typedefvoid(*SIMPLE_SIGNAL)(void*signal,void*pArg);typedefvoid(*SIMPLE_SOLTS)(void*pArg);#defineSIMPLE_SOLTS_T(FuncName)void(FuncName)(void*pArg)#defineSIMPLE_EMIT(signal, arg)if(signal !=NULL)signal(&signal, arg)#defineSIMPLE_SIGNAL_SOLTS_MAX_SOLTS10//一个信号最多连接槽的数量#defineSIMPLE_SIGNAL_SOLTS_MAX_SIGNAL10//信号最多数量
ErrorStatus SimpleSignalSolts_Connect(SIMPLE_SIGNAL *signal, SIMPLE_SOLTS solts);#endif
#include"SimpleSignalSolts.h"#include<string.h>//信号结构typedefstruct{void*signleID;//信号的指针的指针,保存信号的指针,根据指针的地址确定是否是唯一
SIMPLE_SOLTS soltsTable[SIMPLE_SIGNAL_SOLTS_MAX_SOLTS];uint8_t soltsCount;}SIMPLE_SIGNAL_T;//信号表结构typedefstruct{
SIMPLE_SIGNAL_T signalTable[SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL];uint8_t signalCount;}SIMPLE_SIGNAL_SOLTS_HANDLE_T;
SIMPLE_SIGNAL_SOLTS_HANDLE_T handle ={.signalCount =0,};staticvoidSimpleSignalSolts_Signal(void*signal,void*pArg){uint8_t i, j;
SIMPLE_SOLTS solts;for(i =0; i < handle.signalCount; i++)//查找是否是同一个信号{if(handle.signalTable[i].signleID == signal)//这里注意,signleID保存的是指针的地址,{for(j =0; j < handle.signalTable[i].soltsCount; j++){
solts = handle.signalTable[i].soltsTable[j];if(solts !=NULL){solts(pArg);}}}}}/******************************************************************
* @函数说明: 连接信号与槽
* @输入参数: SIMPLE_SIGNAL *singnal 信号的指针(指针的指针)
SIMPLE_SOLTS solts 槽
* @输出参数: 无
* @返回参数: ErrorStatus
******************************************************************/
ErrorStatus SimpleSignalSolts_Connect(SIMPLE_SIGNAL *signal, SIMPLE_SOLTS solts){if(signal ==NULL|| solts ==NULL)//查空{return ERROR;}uint8_t i;if(handle.signalCount > SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL)//错误{
handle.signalCount =0;}for(i =0; i < handle.signalCount; i++)//查找是否是同一个信号{if(handle.signalTable[i].signleID == signal)//这里注意,signleID保存的是指针的地址,{if(handle.signalTable[i].soltsCount > SIMPLE_SIGNAL_SOLTS_MAX_SOLTS){
handle.signalTable[i].soltsCount =0;}if(handle.signalTable[i].soltsCount == SIMPLE_SIGNAL_SOLTS_MAX_SOLTS)//满了{return ERROR;}else{
handle.signalTable[i].soltsTable[handle.signalTable[i].soltsCount++]= solts;//保存槽return SUCCESS;//结束}}}if(handle.signalCount == SIMPLE_SIGNAL_SOLTS_MAX_SIGNAL)//满了{return ERROR;}else{
handle.signalTable[handle.signalCount].signleID = signal;//保存新的信号
handle.signalTable[handle.signalCount].soltsTable[0]= solts;//保存槽
handle.signalTable[handle.signalCount++].soltsCount =1;*signal = SimpleSignalSolts_Signal;return SUCCESS;}}/******************************************************************
* @函数说明: 断开信号槽
* @输入参数: SIMPLE_SIGNAL *singnal 信号的指针(指针的指针)
SIMPLE_SOLTS solts 槽
* @输出参数: 无
* @返回参数: ErrorStatus
******************************************************************/
ErrorStatus SimpleSignalSolts_Disconnect(SIMPLE_SIGNAL *signal, SIMPLE_SOLTS solts){if(signal ==NULL|| solts ==NULL|| handle.signalCount ==0)//查空{return ERROR;}uint8_t i, j;for(i =0; i < handle.signalCount; i++)//查找是否是同一个信号{if(handle.signalTable[i].signleID == signal)//这里注意,signleID保存的是指针的地址,{for(j =0; j < handle.signalTable[i].soltsCount; j++){if(handle.signalTable[i].soltsTable[j]== solts)//找到槽 移除{memcpy(&handle.signalTable[i].soltsTable[j],&handle.signalTable[i].soltsTable[j +1],(handle.signalTable[i].soltsCount - j -1)*sizeof(SIMPLE_SOLTS));
handle.signalTable[i].soltsCount--;}}if(handle.signalTable[i].soltsCount ==0)//此信号没有连接槽了{memcpy(&handle.signalTable[i],&handle.signalTable[i +1],(handle.signalCount - i -1)*sizeof(SIMPLE_SIGNAL_T));
handle.signalCount--;}return SUCCESS;}}return ERROR;}
三:十八般武艺:设计模式
1995年,GOF(Gang of Four) 合著《设计模式》一书。通过书名知道,这本书是写给面向对象语言的,书中以C++作为示例程序,但它的思想不局限于某种语言。虽然过去了27年,新的语言、特性频繁出现,设计模式本身也发生了变化,但对于C这样一个缺少框架、第三方库的语言来讲,设计模式的思想简直是雪中送炭。
设计模式,其实可以理解为:一套可以被反复套用的软件架构。 设计模式对函数指针的使用层出不穷,因为函数指针相当于把函数作为变量操作,我们可以对函数做一切我们对变量做的事,熟练使用函数指针,就可以弄懂设计模式的大半。
设计模式的六大原则
- Single Responsibility Principle:单一职责原则
- Open Closed Principle:开闭原则
- Liskov Substitution Principle:里氏替换原则
- Law of Demeter:迪米特法则
- Interface Segregation Principle:接口隔离原则
- List item Dependence Inversion Principle:依赖倒置原则
把这六个原则的首字母联合起来(两个 L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计。
23种经典设计模式
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
+++创建型模式+++
1、单例模式
单例模式,顾名思义,当重复使用同一个对象(结构体)时,程序中只允许存在一个对象(结构体)的实例,这样能避免反复申请内存,减少系统的开销。
例如员工找老板签署文件,需要反复访问老板这个同一对象:
#include<stdio.h>//定义boss的行为typedefstructBOSS{void(*vfunc)();}s_boss;voidsign(char* str){printf("老板给%s签署文件\n",str);}//封装boss的创建,统一访问void*get_boss(){static s_boss * boss =NULL;//如果系统已经存在对象,直接返回对象指针if(NULL!= boss)return boss;//第一次访问时创建对象
boss =(s_boss *)malloc(sizeof(s_boss));//初始化对象行为
boss->vfunc = sign;return(void*)boss;}voidmain(){//timo找老板签文件
s_boss * boss;
boss =get_boss();
boss->vfunc("timo");//gg找老板签文件
boss =get_boss();
boss->vfunc("gg");}
2、工厂模式(2种)
①简单工厂模式,目的在于实现业务和细节的分离。
通俗得讲,不同工厂都可以实现我的各种业务,并且这些业务都是一样的,只是实现的细节不一样,我可以选择不同的工厂去实现,具体怎么实现的业务层不需要知道,只要选择了一个工厂,我就可以调用我想调用的业务。虽然简单工厂模式不完全符合设计模式的原则,但实际使用的最多。
voidlogin_website(char*str){printf("欢迎登录:%s!\n",str);}voidenter_jlc(){printf("进入嘉立创旗舰店\n");}voidbug_jlc_capacity(char*str){printf("购买嘉立创电容:%s\n",str);}voidenter_jdb(){printf("进入捷多邦旗舰店\n");}voidbug_jdb_capacity(char*str){printf("购买捷多邦电容:%s\n",str);}typedefstructshop_interface{void(*enter)();/*进入商城*/void(*buy)(constchar*str);/*购买物料*/}SHOP_INSTERFACE,*pSHOP_INSTERFACE;/*嘉立创商城*/structjlc{
SHOP_INSTERFACE jlc_interface;/*可扩展其他私有属性*/}/*捷多邦商城*/structjdb{
SHOP_INSTERFACE jdb_interface;/*可扩展其他私有属性*/}
pSHOP_INSTERFACE factory(constchar*str){if("jlc"== str){structjlc*jlc_shop =(structjlc*)malloc(sizeof(structjlc));/*实例化接口*/((pSHOP_INSTERFACE)jlc_shop)->enter = enter_jlc;((pSHOP_INSTERFACE)jlc_shop)->buy = bug_jlc_capacity;return(pSHOP_INSTERFACE)jlc_shop;}elseif("jdb"== str){structjdb*jdb_shop =(structjdb*)malloc(sizeof(structjdb));/*实例化接口*/((SHOP_INSTERFACE*)jdb_shop)->enter = enter_jdb;((SHOP_INSTERFACE*)jdb_shop)->buy = bug_jdb_capacity;return(pSHOP_INSTERFACE)jdb_shop;}}intmain(void){
pSHOP_INSTERFACE shop;/* 登录淘宝网 */login_website("www.taobao.com");/*选择嘉立创商店*/
shop =factory("jlc");/*进入嘉立创旗舰店*/
shop->enter();/*购买嘉立创的0805电容*/
shop->buy("0805");
shop =factory("jdb");/*进入捷多邦旗舰店*/
shop->enter();/*购买捷多邦的0603电容*/
shop->buy("0603");return0;}
②工厂方法模式,只不过是优化了简单工厂模式中if esle的形式,不赘述
③抽象工厂模式,可以理解为工厂的工厂,提供统一的接口让用户根据不同的需求创建出不同的工厂。
首先定义出不同类型的家具以供生产:
voidmodern_chair(void){printf("A modern chair\r\n");}voidmodern_dest(void){printf("A modern desk\r\n");}voidvictorian_chair(void){printf("A victorian chair\r\n");}voidvictorian_dest(void){printf("A victorian desk\r\n");}
其次定义出工厂要制作的家具:
typedefstruct{void(*chair)(void);}Chair_t;typedefstruct{void(*desk)(void);}Desk_t;
设计出工厂的具体生产内容:
Chair_t*make_modern_chair(void){
Chair_t* pChair =(Chair_t)malloc(sizeof(Chair_t));assert(pChair !=NULL);
pChair->chair = modern_chair;return pChair;}
Chair_t*make_victorian_chair(void){
Chair_t* pChair =(Chair_t)malloc(sizeof(Chair_t));assert(pChair !=NULL);
pChair->chair = victorian_chair;return pChair;}
Desk_t*make_modern_desk(void){
Desk_t* pDesk =(Dest_t)malloc(sizeof(Dest_t));assert(pDesk !=NULL);
pDesk->desk = modern_desk;return pDesk;}
Desk_t*make_victorian_desk(void){
Desk_t* pDesk =(Dest_t)malloc(sizeof(Dest_t));assert(pDesk !=NULL);
pDesk->desk = victorian_desk;return pDesk;}
接下来将工厂定义出来:
typedefstruct{Chair_t(*make_chair)(void);Dest_t(*make_desk)(void);}Furniture_factory;
根据用户需求创建不同工厂:
Furniture_factory*Creat_furniture_factory(char* type){
Furniture_factory* pFurniture_factory =(Furniture_factory*)malloc(sizeof(Furniture_factory));assert(pFurniture_factory !=NULL)if(memcmp(type,"modern")==0)//工厂类型{
pFurniture_factory->make_chair = make_modern_chair;//注册方法
pFurniture_factory->make_desk = make_modern_desk;}elseif(memcmp(type,"victorian")==0){
pFurniture_factory->make_chair = make_victorian_chair;
pFurniture_factory->make_desk = make_victorian_desk;}else{printf("type is invalid\r\n");}}
3、建造者模式
建造者模式,主要针对复杂对象,它们拥有多个组成部分,产品的最终形态都是一致的,但是具体组成的每个对象可能是不相同的,建造者模式将复杂对象的表示(抽象)与实现(具象)分离。
如汽车包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
例如使用程序画一个小人,要求有头、身体、手、脚:
#include<stdio.h>//定义小人构建的抽象接口typedefstructpersion_s{void(*build_head)();void(*build_body)();void(*build_hand)();void(*build_foot)();void(*build_persion)(structpersion_s* p);}persion_t;//定义瘦小人构建的接口voiddraw_thin_head(){...}voiddraw_thin_body(){...}voiddraw_thin_hand(){...}voiddraw_thin_foot(){...}//定义胖小人构建的接口voiddraw_fat_head(){...}voiddraw_fat_body(){...}voiddraw_fat_hand(){...}voiddraw_fat_foot(){...}voidbuild_persion(structpersion_s* p){
p->build_head();
p->build_body();
p->build_hand();
p->build_foot();}//主程序voidmain(){//定义构建小人抽象接口persion_t* people;//利用胖小人或者是瘦小人的具体接口来初始化抽象接口
people->build_head = draw_thin_head;
people->build_body = draw_fat_body;...//调用抽象接口中的构建小人方法
people->build_persion(people);}
【注】工厂模式和创建者模式的区别
创建者模式抽象的是复杂对象的组成,所有子对象的实现都可以具象,属于买零件自己回来组装;工厂模式抽象的是业务流程,不同工厂使用自己的一套子业务,属于直接把机器委托给一个喜欢的工厂加工,用什么零件随他便。
4、原型模式
原型模式,可以复制一个个与原型一样的对象。
#include<stdio.h>#include<string.h>#include<stdlib.h>#defineVAR(name, id) name##idtypedefstructPaper{char* teacher_name;int class;char* question;structPaper*(*copy)(structPaper* pPaper);}paper_t;/*原型的复制接口*/paper_t*paper_copy(paper_t* pPaper){paper_t* copy =(paper_t*)malloc(sizeof(paper_t));memcpy(copy, pPaper,sizeof(paper_t));return copy;}paper_t*clone(paper_t* pPaper){return pPaper->copy(pPaper);}voidmain(void){/*定义原型*/paper_t origin_paper ={"Li",5,"1 + 1 = ?", paper_copy};/*复制10份试卷*/for(int i =1; i <=10; i++){paper_t*VAR(paper, i)=clone(&origin_paper);printf("paper_%d => name: %s, class: %d, question: %s\r\n",\
i,VAR(paper, i)->teacher_name,VAR(paper, i)->class,VAR(paper, i)->question);}}
输出:
paper_1 => name: Li, class: 5, question: 1 + 1= ?
paper_2 => name: Li, class: 5, question: 1 + 1= ?
paper_3 => name: Li, class: 5, question: 1 + 1= ?
paper_4 => name: Li, class: 5, question: 1 + 1= ?
paper_5 => name: Li, class: 5, question: 1 + 1= ?
paper_6 => name: Li, class: 5, question: 1 + 1= ?
paper_7 => name: Li, class: 5, question: 1 + 1= ?
paper_8 => name: Li, class: 5, question: 1 + 1= ?
paper_9 => name: Li, class: 5, question: 1 + 1= ?
paper_10 => name: Li, class: 5, question: 1 + 1= ?
+++结构型模式+++
1、适配器模式
适配器模式(Adapter Pattern),是作为两个不兼容的接口之间的桥梁。例如,我们的嵌入式单片机需要兼容两种通信模块:4G和NBIOT,用户可以选配。又或是需要兼容两种操作系统:freertos和ucos。对于单个产品来说,运行时只工作在其中一种状态下,那么和工厂模式等不同的是,我们不需要提供选配工厂的接口,我们用宏开关在预编译阶段选配。
#defineRTOS_TYPE//选配其中一种模式staticvoidcreate_freertos_1(void){printf("create_freertos_1\n");}staticvoidcreate_freertos_2(void){printf("create_freertos_2\n");}staticvoidcreate_ucos(void){printf("create_ucos\n");}staticvoidrun_freertos_1(void){printf("run_freertos_1\n");}staticvoidrun_freertos_2(void){printf("run_freertos_2\n");}staticvoidrun_ucos(void){printf("run_ucos\n");}typedefstruct_OsAdapter{void(* create )(void);void(* run )(void);} OsAdapter;voidcreate(void){#ifdefRTOS_TYPEcreate_freertos_1();create_freertos_2();#elifUCOS_TYPEcreate_ucos();#endif}voidrun(void){#ifdefRTOS_TYPErun_freertos_1();run_freertos_2();#elifUCOS_TYPErun_ucos();#endif}intmain(void){
OsAdapter os;
os.create = create;
os.create();}
2、装饰器模式
装饰器模式,类似面向对象中的多态。在C中,每个函数是一个单独的过程,这个函数编写之后,我们可以为这个函数添加新的功能或者逻辑而不改动原函数,这就实现了类似装饰器的效果了。其实纯粹就是把函数作为可变参数。
#include<stdio.h>//2000年写的函数intadd(int num1,int num2){int sum = num1 + num2;return sum;}//2000年写的函数intsub(int num1,int num2){int sub = num1 - num2;return sub;}//可以继续拓展其他函数 //2008年写的函数intmul(int num1,int num2){int mul = num1* num2;return mul;}/*
使用装饰器模式 在C语言中可以实现类似多态的效果
可以很方便的进行拓展
这个函数 又被称为架构函数
*/voidwapper(int(*func)(int,int),int num1,int num2){//类似回调函数嘛,函数前后 可以进行装饰或者其他逻辑处理func(num1, num2);}intmain(int argc,char*argv[]){wapper(add,20,30);wapper(sub,20,30);wapper(mul,20,30);return0;}
3、代理模式
代理模式,为其他对象提供一种代理以控制对这个对象的访问
关键在于控制
如下面的例子:一个人想要找周杰伦签名或者拍电源或者拍广告必须先经过经纪人而不能直接找周杰伦,而经纪人可以在中间控制一下,比如验证这个人的身份,满足要求才让你见到周杰伦
typedefunsignedint u32;typedefunsignedshort u16;typedefunsignedchar u8;#include"string.h"#include"stdio.h"#include"stdlib.h"//定义接口typedefstruct{//签名char*(*sign)(void*);//拍电影void(*MakeFilm)(void*);//拍广告void(*MakeCommercials)(void*);}BehaviourInterface_t;//周杰伦的实际处理staticchar*zhoujielunsign(void* v){printf("sign ok...\n");return(char*)"Hello";}//周杰伦的实际处理staticvoidzhoujielunMakeFilm(void* v){printf("MakeFilm ok...\n");}//周杰伦的实际处理staticvoidzhoujielunMakeCommercials(void* v){printf("MakeCommercials ok...\n");}//实例创建typedefstruct{//代理对象相当于经纪人
BehaviourInterface_t Proxy;//实际的对象周杰伦
BehaviourInterface_t zhoujielun;//控制对象的变量int age;int sex;char CompanyName[32];}BehaviourProxyNew_t;//经纪的实际处理staticchar*Proxysign(void* v){
BehaviourProxyNew_t* p =(BehaviourProxyNew_t*)v;//如果小于18岁或者是男的话就不给你签名,经纪人在这里控制了一下if(p->age <18|| p->sex ==1){printf("You are too young or you are man,no sign thank you...\n");return(char*)"You are too young or you are man,no sign thank you";}else{return p->zhoujielun.sign(v);}returnNULL;}//经纪的实际处理staticvoidProxyMakeFilm(void* v){
BehaviourProxyNew_t* p =(BehaviourProxyNew_t*)v;//如果是小公司的话就不拍,我的艺人就是这么豪横,经纪人在这里控制了一下if(strcmp(p->CompanyName,"small")==0){printf("sorry you company is so small...\n");}elseif(strcmp(p->CompanyName,"big")==0){
p->zhoujielun.MakeFilm(v);}}//经纪的实际处理staticvoidProxyCommercials(void* v){
BehaviourProxyNew_t* p =(BehaviourProxyNew_t*)v;//如果是小公司的话就不怕,我的艺人就是这么豪横,经纪人在这里控制了一下if(strcmp(p->CompanyName,"small")==0){printf("sorry you company is so small...\n");}elseif(strcmp(p->CompanyName,"big")==0){
p->zhoujielun.MakeCommercials(v);}}//代理创建static BehaviourProxyNew_t*NewProxy(int age,int sex,constchar* CompanyName){
BehaviourProxyNew_t *p =(BehaviourProxyNew_t*)malloc(sizeof(BehaviourProxyNew_t));//经纪人实现接口 相当于代理对象
p->Proxy.sign = Proxysign;
p->Proxy.MakeFilm = ProxyMakeFilm;
p->Proxy.MakeCommercials = ProxyCommercials;//周杰伦实现接口 相对于实际对象
p->zhoujielun.sign = zhoujielunsign;
p->zhoujielun.MakeFilm = zhoujielunMakeFilm;
p->zhoujielun.MakeCommercials = zhoujielunMakeCommercials;//经纪人独有的控制变量
p->age = age;
p->sex = sex;memcpy(p->CompanyName,CompanyName,strlen(CompanyName));return p;}intmain(void){//第一个代理处理名为hansen的人 男性 27岁 大公司的人
BehaviourInterface_t *HansenProxy =(BehaviourInterface_t*)NewProxy(27,1,"big");//1:male//第二个人名为Maria的人,女性 22岁,小公司的人
BehaviourInterface_t *MariaProxy =(BehaviourInterface_t*)NewProxy(22,0,"small");//0:femaleprintf("hansenProxy----------------------------------------------\n");
HansenProxy->sign(HansenProxy);
HansenProxy->MakeFilm(HansenProxy);
HansenProxy->MakeCommercials(HansenProxy);printf("\n\nMariaProxy-----------------------------------------------\n");
MariaProxy->sign(MariaProxy);
MariaProxy->MakeFilm(MariaProxy);
MariaProxy->MakeCommercials(MariaProxy);}
【注意】
①结构体指针向下强转操作:
BehaviourInterface_t *HansenProxy =(BehaviourInterface_t*)NewProxy(27,1,"big");//1:male
BehaviourInterface_t *MariaProxy =(BehaviourInterface_t*)NewProxy(22,0,"small");//0:female
②结构体指针向上强转操作:
BehaviourProxyNew_t* p =(BehaviourProxyNew_t*)v;
4、外观模式
外观模式(Facade Pattern),隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
typedefstruct_Computer{void(*work )(void);} Computer;voidwork(void){printf("work here!\n");}typedefstruct_Phone{void(*contact )(void);} Phone;voidcontact(void){printf("contact here!\n");}typedefstruct_Factory{
Computer *computer;
Phone *phone;void(*create )(struct_Factory*factory );} Factory;voidcreate( Factory *factory ){assert(NULL!= factory );
factory->computer->work();
factory->phone->contact();}intmain(void){
Computer computer ={.work = work,};
Phone phone ={.contact = contact,};
Factory factory ={.create = create,.computer =&computer,.phone =&phone,};
factory.create(&factory );}
5、桥接模式
桥接(Bridge),是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
typedefstruct_MeatDumpling{void(*make)();}MeatDumpling;typedefstruct_NormalDumpling{void(*make)();}NormalDumpling;typedefstruct_DumplingReuqest{int type;void* pDumpling;}DumplingRequest;voidbuy_dumpling(DumplingReuqest* pDumplingRequest){assert(NULL!= pDumplingRequest);if(MEAT_TYPE == pDumplingRequest->type)return(MeatDumpling*)(pDumplingRequest->pDumpling)->make();elsereturn(NormalDumpling*)(pDumplingRequest->pDumpling)->make();}
这里定义了一个饺子买卖的唯一接口。它的特别支持就在于两个地方,第一是我们定义了饺子的类型type,这个type是可以随便扩充的;第二就是这里的pDumpling是一个void*指针,只有把它和具体的dumpling绑定才会衍生出具体的含义。
6、组合模式
组合模式(Composite Pattern),将对象组合成树形结构来表示“整体-部分”的层次结构,数据结构中的链表、二叉树,其实都可以用组合模式来解释。
typedefstruct_NODE{void* pData;struct_NODE* left;struct_NODE* right;}NODE;
7、享元模式
享元模式(Flyweight Pattern),主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式看上去有点玄乎,但是其实也没有那么复杂。我们还是用示例说话。比如说,大家在使用电脑的使用应该少不了使用WORD软件。使用WORD呢, 那就少不了设置模板。什么模板呢,比如说标题的模板,正文的模板等等。这些模板呢,又包括很多的内容。哪些方面呢,比如说字体、标号、字距、行距、大小等等。
typedefstruct_Font{int type;int sequence;int gap;int lineDistance;void(*operate)(struct_Font* pFont);}Font;
上面的Font表示了各种Font的模板形式。所以,下面的方法就是定制一个FontFactory的结构。
typedefstruct_FontFactory{
Font** ppFont;int number;int size;
Font*GetFont(struct_FontFactory* pFontFactory,int type,int sequence,int gap,int lineDistance);}FontFactory;
这里的GetFont即使对当前的Font进行判断,如果Font存在,那么返回;否则创建一个新的Font模式。
Font*GetFont(struct_FontFactory* pFontFactory,int type,int sequence,int gap,int lineDistance){int index;
Font* pFont;
Font* ppFont;if(NULL== pFontFactory)returnNULL;for(index =0; index < pFontFactory->number; index++){if(type != pFontFactory->ppFont[index]->type)continue;if(sequence != pFontFactory->ppFont[index]->sequence)continue;if(gap != pFontFactory->ppFont[index]->gap)continue;if(lineDistance != pFontFactory->ppFont[index]->lineDistance)continue;return pFontFactory->ppFont[index];}
pFont =(Font*)malloc(sizeof(Font));assert(NULL!= pFont);
pFont->type = type;
pFont->sequence = sequence;
pFont->gap = gap;
pFont->lineDistance = lineDistance;if(pFontFactory-> number < pFontFactory->size){
pFontFactory->ppFont[index]= pFont;
pFontFactory->number ++;return pFont;}
ppFont =(Font**)malloc(sizeof(Font*)* pFontFactory->size *2);assert(NULL!= ppFont);memmove(ppFont, pFontFacoty->ppFont, pFontFactory->size);free(pFontFactory->ppFont);
pFontFactory->size *=2;
pFontFactory->number ++;
ppFontFactory->ppFont = ppFont;return pFont;}
亨元模式和单例模式有点像,但有差别:
1、其实现方式不一样,单例是一个类只有一个唯一的实例,而享元可以有多个实例,只是通过一个共享容器来存储不同的对象。
2、其使用场景不一样,单例是强调减少实例化提升性能,因此一般用于一些需要频繁创建和销毁实例化对象或创建和销毁实例化对象非常消耗资源的类中,如连接池、线程池。而享元则是强调共享相同对象或对象属性,节约内存使用空间。
+++行为型模式+++
1、策略模式
算法可以自由切换,这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。
//抽象的策略角色typedefstruct_Strategy{void(*doSomething)();}Strategy;voiddoSomething1(){printf("具体策略1的运算法则");}voiddoSomething2(){printf("具体策略2的运算法则");}//具体策略角色struct_Strategy ConcreteStrategy1 ={.doSomething = doSomething1,};struct_Strategy ConcreteStrategy2 ={.doSomething = doSomething2,};//封装角色typedefstruct_Context{
Strategy *pStrategy;}Context;voiddoAnything(Strategy *pStrategy){if(NULL== pStrategy)return;
pStrategy->doSomething();}void client
{
Context con;
Strategy *pStrategy1 =&ConcreteStrategy1;
con.pStrategy = pStrategy1;doAnything(con.pStrategy);
Strategy *pStrategy2 =&ConcreteStrategy2;
con.pStrategy = pStrategy2;doAnything(con.pStrategy);}
如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要 使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。
2、模板方法模式
在嵌入式的应用场景中,管理资源(例如文件、内存)是一件非常麻烦、非常容易出错的事情。因为在分配资源后,还必须释放资源。例如fopen()打开文件后,必须要使用fclose()来关闭文件,而使用malloc申请内存资源后,就必须使用free()函数来释放内存。
在实际开发工作中,稍微对malloc不注意就会导致内存泄漏。而模板方法模式堪称预防这类低级错误的神器!
场景:现在硬盘卡上存放了多部电影,我们需要在电脑上随机读取播放。假设我们动态申请1G的内存空间来存放视频,如果女主是美女,那么正常播放视频,播放完后退出程序。如果女主长相感人,则立马退出程序!
传统做法:
...intmain(){...//申请1G内存
p_movie =malloc(1G);//读取sd卡视频,并存放在p_movie中...//根据电影类型来选择播放方式if(p_movie == 美女){..._/正常播放电影
}elseif(p_movie == 长相感人){..._/停止播放电影
//释放内存free(p_movie);return-1;}//释放内存free(p_movie);return0;}
在上面的代码实现中,管理内存和使用内存的代码耦合在一起。在每个分支情况里面,必须时刻注意内存的使用和释放情况(比如在本例中,free函数就出现了两次)。随着各种程序中的分支越来越多、越来越庞大,有时候很容易忽略对内存的释放,从而引起内存泄漏。
使用模板方法:
//资源使用代码intact_movie(char* p){if(p == 美女){..._/正常播放电影
}elseif(p == 长相感人){..._/停止播放电影
return-1;}return0;}//定义模板方法函数,负责资源管理inttemplate(int(*play_movie)(char* p){int ret;//申请1G内存
p_movie =malloc(1G);//读取sd卡视频,并存放在p_movie中...//根据电影类型来选择播放方式
ret =play_movie(p_movie);//释放内存free(p_movie);return ret;}intmain(){int ret;...//调用模板方法函数
ret =template(act_movie);...return ret;}
3、观察者模式(重要)
观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。有点类似发布订阅的机制。下面详细说明一下实现步骤:
①建立一个抽象的观察者
typedef void (*observer_handle)(void* msg);
。为了满足开闭原则,方便我们对观察者进行扩展,我们维护一个全局的观察者链表
Observer_Node* func_head_node[TOPIC_MAX] = {NULL};
,这个链表由多个观察者节点组成,数量取决于主题枚举
topic_e
的数量
TOPIC_MAX
,我们通知消息通过的就是这个主题。
②那么接下来,我们需要根据抽象观察者写出我们的实际具体观察者
void ObsOnOff(void *msg)
,将具体实际观察者注册到链表中,但这时观察者函数相当于空罐子,我们只是把他摆到院子里的台阶上(链表),等待雨天(通知)来了好接雨水(msg)。
③消息通知时,通过主题查找观察者链表,调用对应的具体的观察者,并将msg传递给它,具体的观察者就可以处理msg了。
所以,所谓的观察者其实是定义好一个没有传入数据的回调函数,他的主要任务仍然是处理数据,只是他需要在通知了具体消息时,来找到对应的函数来处理,前提是我们把主题和观察者绑定在了同一节点上。
#ifndef__MSG_SUBSCRIBE_H_#define__MSG_SUBSCRIBE_H_/*消息的类型都在这个枚举里*/typedefenum{
ON_OFF_STATE,
LIGHTNESS,
WORK_MODE,
TOPIC_MAX//方便计数}topic_e;/*抽象的观察者*/typedefvoid(*observer_handle)(void* msg);typedefstruct{
observer_handle obs;
topic_e topic;}observer_t;/*主题订阅(添加观察者)*/inttopic_subscribe(observer_t*observer);/*消息通知*/intmsg_notify(topic_e topic,void*msg);#endif/* __MSG_SUBSCRIBE_H_ */
#include<stdio.h>#include"msg_subscribe.h"/*观察者节点*/typedefstruct_Link_List{
observer_handle obs;struct_Link_List* next;}Observer_Node;/*全局的观察者链表*/
Observer_Node* func_head_node[TOPIC_MAX]={NULL};/*主题订阅(添加注册观察者)*/inttopic_subscribe(observer_t* observer){assert(observer->topic >= TOPIC_MAX || observer->obs ==NULL|| observer ==NULL);//创建观察者
Observer_Node* observer_node =(Observer_Node*)malloc(sizeof(Observer_Node));assert(observer_node ==NULL)
observer_node->obs = observer->obs;
observer_node->next =NULL;//指向订阅主题(要观察的主题)对应的节点
Observer_Node* func_link_tail = func_head_node[observer->topic];//找到没有被占用的节点if(func_link_tail !=NULL){while(func_link_tail->next !=NULL){
func_link_tail = func_link_tail->next;}}//如果找到了空节点,则注册观察者if(func_link_tail ==NULL){
func_head_node[observer->topic]= observer_node;}//没有空节点,就覆盖下一个else{
func_link_tail->next = observer_node;}return0;}/*消息通知*/intmsg_notify(topic_e topic,void* msg){assert(topic >= TOPIC_MAX);
Observer_Node* cur_node = func_head_node[topic];printf("Msg notify %d\r\n", topic);while(cur_node !=NULL){assert(cur_node->obs ==NULL);
cur_node->obs(msg);
cur_node = cur_node->next;}return0;}/*具体观察者*/voidObsOnOff(void* msg){//消息处理printf("观察到的消息:%s\n",msg);}observer_t obsOnOff ={.obs = ObsOnOff,.topic = ON_OFF_STATE,}mian(){topic_subscribe(&obsOnOff);msg_notify(ON_OFF_STATE,"开关动作");}
详细演化过程参考链接:C语言和设计模式-观察者模式
4、迭代器模式
略
5、责任链模式
在责任链模式中,优先级决定了那个handler会被先调,哪些会被后调用。在扩展特性里,每个handler可以有不处理和处理完之后继续交给下一个handler两种选择。如果该事件最后没有被消费,会有一个异常处理函数。如果责任链上任意一个handler消费了事件,那么就不传给下一个handler,直接结束。
最典型的责任链有linux内核的中断处理机制的纯软件部分和内核网络netfiler的HOOK机制,使用AT指令集时也常用到。
责任链模式,逻辑上和其最相近的一个设计模式为观察者模式。观察者模式和责任链模式的最大的差别在于,事件会被通知到每一个平等的handler,而不是逐级处理,也不存在优先级的说法,也不会出现事件没有处理需要异常函数收尾。
#include<stdio.h>#include<assert.h>#include"list.h"staticLIST_HEAD(chain_head);//定义并初始化一个头节点enum{
PASS,
REFUSE,};typedefstruct{int money;char* files;}info_t;typedefint(*request_handle)(info_t* info);typedefstruct{
request_handle func;
ListObj list;}chain_node_t;intleader(info_t* info){assert(info !=NULL);if(info->money <1000)//金额小于1000就行{return PASS;}return REFUSE;}inthr(info_t* info){assert(info !=NULL);if(info->money <2000&& info->files !=NULL)//金额小于2000且材料齐全{return PASS;}return REFUSE;}intboss(info_t* info){assert(info !=NULL);if(info->files !=NULL)//材料齐全就行,不差钱{return PASS;}return REFUSE;}//审批流程: leader->hr->bosschain_node_t req_table[]={{.func = leader},{.func = hr},{.func = boss}};voidmain(void){/*将审批节点添加到责任链上*/for(int i =0; i <sizeof(req_table)/sizeof(req_table[0]); i++){list_insert_before(&chain_head,&(req_table[i].list));}/*报销内容*/info_t info ={.money =900,.files ="files"};
ListObj *node;list_for_each(node,&chain_head){chain_node_t* req =list_entry(node,chain_node_t, list);//依次审批if(req->func(&info)!= PASS){/*报销失败*/printf("Failed\r\n");return;}}/*报销成功*/printf("Success\r\n");return;}
6、命令模式
命令模式,类似于带函数指针的查表法,每个命令对应一个函数指针。
经典案例:
1、按键处理,每个按键按下得到一个索引(指的就是命令),一个按键对应一个处理函数。按键处理命令模式
2、协议解析(串口,网口,CAN,等等);以串口为例简单说明一下,比如有如下协议:
帧头命令数据长度数据校验帧尾1字节1字节2字节n字节2字节1字节
// 当心字节对齐的问题typedefstruct{uint8_t head;uint8_t cmd;uint16_t length;uint8_t data[1];}package_t;staticintparse_temperature(char*buffer){int value =*(int*)buffer;printf("temperature = %d\n", value);}staticintparse_humidity(char*buffer){int value =*(int*)buffer;printf("humidity = %d\n", value);}staticintparse_illumination(char*buffer){int value =*(int*)buffer;printf("illumination = %d\n", value);}typedefstruct{uint8_t cmd;void(* handle)(char*buffer);}package_entry_t;staticconstpackage_entry_t package_items[]={{0x01, parse_temperature},{0x02, parse_humidity},{0x03, parse_illumination},{0xFF,NULL},};staticuint8_tparse(char*buffer,uint16_t length){package_t*frame =(package_t*)buffer;uint16_t crc =CRCCheck(buffer, length -3);uint8_t tail = buffer[length -1];constpackage_entry_t*entry;if((frame->head != xxx)&&(tail != xxx)&&(crc !=(buffer[length -3])<<8| buffer[length -2])){return0;}for(entry = package_items; entry->handle !=NULL;++entry){if(frame->cmd == entry->cmd){
entry->handle(frame->data);break;}}return1;}
7、备忘录模式
备忘录模式,是为了支持撤销动作而存在的。作为了解。
撤销动作的结构体:
typedefstruct_Action{int type;void* pData;void(*process)(void* pData);struct_Action* next;}Action;
操作者结构体:
typedefstruct_Organizer{int number;
Action* pActionHead;
Action*(*create)();void(*restore)(struct_Organizer* pOrganizer);//撤销函数,参数为该操作者结构体}Organizer;
撤销动作:
voidrestore(struct_Organizer* pOrganizer){
Action* pHead;assert(NULL!= pOrganizer);
pHead = pOrganizer->pActionHead;
pHead->process(pHead->pData);
pOrganizer->pActionHead = pHead->next;
pOrganizer->number --;free(pHead);return;}
8、状态模式
状态模式,就是前面提到的状态机。
9、访问者模式
访问者模式,就是不同函数操作同一对象,产生不同的作用。
被访问对象:
typedefstruct_Tofu{int type;void(*eat)(struct_Visitor* pVisitor,struct_Tofu* pTofu);}Tofu;
访问者:
typedefstruct_Visitor{int region;void(*process)(struct_Tofu* pTofu,struct_Visitor* pVisitor);}Visitor;
voideat(struct_Visitor* pVisitor,struct_Tofu* pTofu){assert(NULL!= pVisitor &&NULL!= pTofu);
pVisitor->process(pTofu, pVisitor);}
voidprocess(struct_Tofu* pTofu,struct_Visitor* pVisitor){assert(NULL!= pTofu &&NULL!= pVisitor);if(pTofu->type == SPICY_FOOD && pVisitor->region == WEST ||
pTofu->type == STRONG_SMELL_FOOD && pVisitor->region == EAST){printf("I like this food!\n");return;}printf("I hate this food!\n");}
10、中介者模式
中介者模式,用于多个独立个体产生大量数据交流的情况,数据都转发给中介者,会大大降低代码交互复杂度。中介者就像媒婆的作用,在男女间传话。
媒婆、人:
struct_Mediator{
People* man;
People* woman;};struct_People{
Mediator* pMediator;/* 这里使用不完整声明,struct _Mediator 还没有定义,但编译器可以接受*/void(*request)(struct_People* pPeople);void(*process)(struct_Peoplle* pPeople);};typedefstruct_Mediator Mediator;typedefstruct_People People;
【参考链接】结构体相互嵌套使用方法
男方通过媒婆对女方提出要求:
voidman_request(struct_People* pPeople){assert(NULL!= pPeople);
pPeople->pMediator->woman->process(pPeople->pMediator->woman);}
女方通过媒婆对南方提出要求:
voidwoman_request(struct_People* pPeople){assert(NULL!= pPeople);
pPeople->pMediator->man->process(pPeople->pMediator->man);}
11、解释器模式
解释器模式,提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
我们知道在C语言中,关于变量的定义是这样的:一个不以数字开始的由字母、数字和下划线构成的字符串。这种形式的表达式可以用状态自动机解决,当然也可以用解释器的方式解决。
typedefstruct_Interpret{int type;void*(*process)(void* pData,int* type,int* result);}Interpret;
这里可以定义成字母的解释器、数字解释器、下划线解释器三种形式。所以,我们可以进一步定义一下process的相关函数。
对于数字解释器来说,它只解释数字,遇到非数字就退出,让别的解释器继续解释。
#defineDIGITAL_TYPE1#defineLETTER_TYPE2#defineBOTTOM_LINE3void*digital_process(void* pData,int* type,int* result){
UINT8* str;assert(NULL!= pData &&NULL!= type &&NULL!= result);
str =(UNT8*)pData;while(*str >='0'&&*str <='9'){
str ++;}if(*str =='\0'){*result = TRUE;returnNULL;}if(*str =='_'){*result = TRUE;*type = BOTTOM_TYPE;return str;}if(*str >='a'&&*str <='z'||*str >='A'&&*str <='Z'){*result = TRUE;*type = LETTER_TYPE;return str;}*result = FALSE;returnNULL;}void*letter_process(void* pData,int* type,int* result){
UINT8* str;assert(NULL!= pData &&NULL!= type &&NULL!= result);
str =(UNT8*)pData;while(*str >='a'&&*str <='z'||*str >='A'&&*str <='Z'){
str ++;}if(*str =='\0'){*result = TRUE;returnNULL;}if(*str =='_'){*result = TRUE;*type = BOTTOM_TYPE;return str;}if(*str >='0'&&*str <='9'){*result = TRUE;*type = DIGITAL_TYPE;return str;}*result = FALSE;returnNULL;}void*bottom_process(void* pData,int* type,int* result){
UINT8* str;assert(NULL!= pData &&NULL!= type &&NULL!= result);
str =(UNT8*)pData;while('_'==*str ){
str ++;}if(*str =='\0'){*result = TRUE;returnNULL;}if(*str >='a'&&*str <='z'||*str >='A'&&*str <='Z'){*result = TRUE;*type = LETTER_TYPE;return str;}if(*str >='0'&&*str <='9'){*result = TRUE;*type = DIGITAL_TYPE;return str;}*result = FALSE;returnNULL;}
四、鸡肋的吃法:面向对象
众所周知,C语言时面向过程的语言,那有没有必要面向对象呢?他确实很好的解决了C的一些痛点,但在一些小型项目中,面向对象这个东西显得臃肿、复杂,如同鸡肋”食之无味,弃之可惜“。所以切记不要为了面向对象而面向对象,更不要为了装杯而面向对象,我们应该酌情使用。
**感兴趣可以去了解轻量级的C语言面向对象编程框架
lw_oopc
**
链接:https://github.com/Akagi201/lw_oopc
我们这里自己简单实现一下面向对象的封装、继承和多态。建议使用现成框架。
1、封装
structA_private;structA{int a;...void(*func)(structA*);structA_private* priv;};/*上面的代码,我们只前向声明结构体 struct A_private ,没人知道它里面具体是什么东西。假如 struct A 对应的头
文件是 A.h ,那么把 A_private 的声明放在 A_p.h 中,A.c 文件包含 A_p.h ,那么在实现 struct A 的函数指针 func
时如何使用 struct A_private ,客户程序员根本无须知道。
这样做的好处是显而易见的,除了预定义好的接口,客户程序员完全不需要知道实现细节,即便实现经过重构完全重来,
客户程序员也不需要关注,甚至相应的模块连重新编译都不要——因为 A.h 自始至终都没变过。
*/
2、继承
//内存管理类new.h#ifndefNEW_H#defineNEW_Hvoid*new(constvoid*class,...);voiddelete(void*item);voiddraw(constvoid*self);#endif//内存管理类的C文件:new.c#include“new.h”#include“base.h”void*new(constvoid*_base,...){conststructBase*base = _base;void*p =calloc(1, base->size);assert(p);*(conststructBase**)p = base;if(base->ctor){
va_list ap;va_start(ap, _base);
p = base->ctor(p,&ap);va_end(ap);}return p;}voiddelete(void*self){conststructBase**cp = self;if(self &&*cp &&(*cp) —> dtor)
self =(*cp) —>dtor(self);free(self);}voiddraw(constvoid*self){conststructBase*const*cp = self;assert(self &&*cp &&(*cp)->draw);(*cp)->draw(self);}//基类:base.h#ifndefBASE_H#defineBASE_HstructBase{
size_t size;//类所占空间void*(*ctor)(void*self, va_list *app);//构造函数void*(*dtor)(void*self);//析构函数void(*draw)(constvoid*self);//作图};#endif// Point头文件(对外提供的接口):point.h#ifndefPOINT_H#definePOINT_Hexternconstvoid*Point;/* 使用方法:new (Point, x, y); */#endif// Point内部头文件(外面看不到):point.r#ifndefPOINT_R#definePOINT_RstructPoint{constvoid*base;//继承,基类指针,放在第一个位置,const是防止修改int x, y;//坐标};#endif// Point的C文件:point.c#include“point.h”#include“new.h”#include“point.h”#include“point.r”staticvoid*Point_ctor(void*_self, va_list *app){structPoint*self = _self;
self->x =va_arg(*app,int);
self->y =va_arg(*app,int);return self;}staticvoidPoint_draw(constvoid*_self){conststructPoint*self = _self;printf(“draw(% d,% d)”, self->x, self->y);}staticconststructBase _Point ={sizeof(structPoint), Point_ctor,0, Point_draw
};constvoid*Point =&_Point;//测试程序:main.c#include“point.h”#include“new.h”intmain(int argc,char**argv){void*p =new(Point,1,2);draw(p);delete(p);}
3、多态
structbase_class{int a;void(*vfunc)(int a);}voidbase_class_vfunc(structbase_class*self,int a){assert(self !=NULL);assert(slef->vfunc !=NULL);
self->vfunc(a);}structchild_class{structbase_class parent;int b;};voidchild_class_init(structchild_class*self){structbase_class*parent;
parent =(struct_base_class *)self;assert(parent !=NULL);
parent->vfunc = child_class_vfunc;}staticvoidchild_class_vfunc(structchild_class*self,int a){
self->b = a +10;}
五、垃圾分类:防御式编程
分层设计或者模块化编程之后,带来另外一个问题,接口的开发者和接口的使用者往往太信任对方,例如我们自定义加法运算接口如下:
int add(int a,int b)(int sum = a+b ;return sun;)
,使用者使用如下:
int s = add(65535,1)
,将必然导致溢出错误。
防御式编程的中心思想是: 子程序不因传入错误数据而被破坏,哪怕是有其他程序产生的错误数据。
1.非法输入
对已形成产品的软件而言,应该做到“垃圾进,什么都不出”、“进来垃圾,出去是出错提示”或“不许垃圾进来”。通常有三种方法来处理进来垃圾的情况。
2.使用断言(assert)
断言可以用于在代码中说明各种假定,澄清各种不希望的情形。但通常断言只是在开发阶段用于帮助查清相互矛盾的假定、预料之外的情况以及传给子程序的错误数据等。在生成产品代码时,不要把断言编译进目标代码,以免降低性能和让用户看到断言报错信息。下面是关于使用断言的一些指导性建议。
- 用错误处理代码来处理预期会发生的状况,用断言来处理绝不应该发生的状况
- 避免把需要执行的代码放到断言中:断言只检查变量的值,而不要在断言中运行函数
- 用断言来注解并验证前条件和后条件:所谓前条件是子程序或类的调用方代码在调用子程序或实例化对象之前要确保为真的属性,后条件是子程序或类在执行结束后要确保为真的属性
- 对于高健壮性的代码,应该先使用断言再处理错误
版权归原作者 外来务工人员徐某 所有, 如有侵权,请联系我们删除。