1. 概念提出
所謂表驅(qū)動(dòng)法(Table-Driven Approach)簡(jiǎn)而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫(kù)的一種體現(xiàn)。
根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動(dòng)法,即以每個(gè)字的字形為依據(jù),計(jì)算出一個(gè)索引值,并映射到對(duì)應(yīng)的頁(yè)數(shù)。相比一頁(yè)一頁(yè)地順序翻字典查字,部首檢字法效率極高。
具體到編程方面,在數(shù)據(jù)不多時(shí)可用邏輯判斷語(yǔ)句(if…else或switch…case)來(lái)獲取值;但隨著數(shù)據(jù)的增多,邏輯語(yǔ)句會(huì)越來(lái)越長(zhǎng),此時(shí)表驅(qū)動(dòng)法的優(yōu)勢(shì)就開始顯現(xiàn)。例如,用36進(jìn)制(A表示10,B表示11,…)表示更大的數(shù)字,邏輯判斷語(yǔ)句如下:
if(ucNum10) { ????ucNumChar?=?ConvertToChar(ucNum); } else?if(ucNum?==?10) { ????ucNumChar?=?'A'; } else?if(ucNum?==?11) { ????ucNumChar?=?'B'; } else?if(ucNum?==?12) { ????ucNumChar?=?'C'; } //...?... else?if(ucNum?==?35) { ????ucNumChar?=?'Z'; }
當(dāng)然也可以用switch…case結(jié)構(gòu),但實(shí)現(xiàn)都很冗長(zhǎng)。而用表驅(qū)動(dòng)法(將numChar存入數(shù)組)則非常直觀和簡(jiǎn)潔。如:
CHARaNumChars[]={'0','1','2',/*3~9*/'A','B','C',/*D~Y*/'Z'}; CHARucNumChar=aNumChars[ucNum%sizeof(aNumChars)];
像這樣直接將變量當(dāng)作下數(shù)組下標(biāo)來(lái)讀取數(shù)值的方法就是直接查表法。
注意,如果熟悉字符串操作,則上述寫法可以更簡(jiǎn)潔:
CHARucNumChar="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[ucNum];
使用表驅(qū)動(dòng)法時(shí)需要關(guān)注兩個(gè)問(wèn)題:一是如何查表,從表中讀取正確的數(shù)據(jù);二是表里存放什么,如數(shù)值或函數(shù)指針。
常用的查表方式有直接查找、索引查找和分段查找等。
1.1.1 直接查找
即直接通過(guò)數(shù)組下標(biāo)獲取到數(shù)據(jù)。如果熟悉哈希表的話,可以很容易看出這種查表方式就是哈希表的直接訪問(wèn)法。
如獲取星期名稱,邏輯判斷語(yǔ)句如下:
if(0==ucDay) { pszDayName="Sunday"; } elseif(1==ucDay) { pszDayName="Monday"; } //...... elseif(6==ucDay) { pszDayName="Saturday"; }
而實(shí)現(xiàn)同樣的功能,可將這些數(shù)據(jù)存儲(chǔ)到一個(gè)表里:
CHAR*paNumChars[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; CHAR*pszDayName=paNumChars[ucDay];
類似哈希表特性,表驅(qū)動(dòng)法適用于無(wú)需有序遍歷數(shù)據(jù),且數(shù)據(jù)量大小可提前預(yù)測(cè)的情況。
對(duì)于過(guò)于復(fù)雜和龐大的判斷,可將數(shù)據(jù)存為文件,需要時(shí)加載文件初始化數(shù)組,從而在不修改程序的情況下調(diào)整里面的數(shù)值。
有時(shí),訪問(wèn)之前需要先進(jìn)行一次鍵值轉(zhuǎn)換。如表驅(qū)動(dòng)法表示端口忙閑時(shí),需將槽位端口號(hào)映射為全局編號(hào)。所生成的端口數(shù)目大小的數(shù)組,其下標(biāo)對(duì)應(yīng)全局端口編號(hào),元素值表示相應(yīng)端口的忙閑狀態(tài)。
1.1.2 索引查找
有時(shí)通過(guò)一次鍵值轉(zhuǎn)換,依然無(wú)法把數(shù)據(jù)(如英文單詞等)轉(zhuǎn)為鍵值。此時(shí)可將轉(zhuǎn)換的對(duì)應(yīng)關(guān)系寫到一個(gè)索引表里,即索引訪問(wèn)。
如現(xiàn)有100件商品,4位編號(hào),范圍從0000到9999。此時(shí)只需要申請(qǐng)一個(gè)長(zhǎng)度為100的數(shù)組,且對(duì)應(yīng)2位鍵值。但將4位的編號(hào)轉(zhuǎn)換為2位的鍵值,可能過(guò)于復(fù)雜或沒(méi)有規(guī)律,最合適的方法是建立一個(gè)保存該轉(zhuǎn)換關(guān)系的索引表。采用索引訪問(wèn)既節(jié)省內(nèi)存,又方便維護(hù)。比如索引A表示通過(guò)名稱訪問(wèn),索引B表示通過(guò)編號(hào)訪問(wèn)。
1.1.3 分段查找
通過(guò)確定數(shù)據(jù)所處的范圍確定分類(下標(biāo))。有的數(shù)據(jù)可分成若干區(qū)間,即具有階梯性,如分?jǐn)?shù)等級(jí)。此時(shí)可將每個(gè)區(qū)間的上限(或下限)存到一個(gè)表中,將對(duì)應(yīng)的值存到另一表中,通過(guò)第一個(gè)表確定所處的區(qū)段,再由區(qū)段下標(biāo)在第二個(gè)表里讀取相應(yīng)數(shù)值。注意要留意端點(diǎn),可用二分法查找,另外可考慮通過(guò)索引方法來(lái)代替。
如根據(jù)分?jǐn)?shù)查績(jī)效等級(jí):
#defineMAX_GRADE_LEVEL(INT8U)5 DOUBLEaRangeLimit[MAX_GRADE_LEVEL]={50.0,60.0,70.0,80.0,100.0}; CHAR*paGrades[MAX_GRADE_LEVEL]={"Fail","Pass","Credit","Distinction","HighDistinction"}; staticCHAR*EvaluateGrade(DOUBLEdScore) { INT8UucLevel=0; for(;ucLevel
上述兩張表(數(shù)組)也可合并為一張表(結(jié)構(gòu)體數(shù)組),如下所示:
typedefstruct{ DOUBLEaRangeLimit; CHAR*pszGrade; }T_GRADE_MAP; T_GRADE_MAPgGradeMap[MAX_GRADE_LEVEL]={ {50.0,"Fail"}, {60.0,"Pass"}, {70.0,"Credit"}, {80.0,"Distinction"}, {100.0,"HighDistinction"} }; staticCHAR*EvaluateGrade(DOUBLEdScore) { INT8UucLevel=0; for(;ucLevel
該表結(jié)構(gòu)已具備的數(shù)據(jù)庫(kù)的雛形,并可擴(kuò)展支持更為復(fù)雜的數(shù)據(jù)。其查表方式通常為索引查找,偶爾也為分段查找;當(dāng)索引具有規(guī)律性(如連續(xù)整數(shù))時(shí),退化為直接查找。
使用分段查找法時(shí)應(yīng)注意邊界,將每一分段范圍的上界值都考慮在內(nèi)。找出所有不在最高一級(jí)范圍內(nèi)的值,然后把剩下的值全部歸入最高一級(jí)中。有時(shí)需要人為地為最高一級(jí)范圍添加一個(gè)上界。
同時(shí)應(yīng)小心不要錯(cuò)誤地用“<”來(lái)代替“<=”。要保證循環(huán)在找出屬于最高一級(jí)范圍內(nèi)的值后恰當(dāng)?shù)亟Y(jié)束,同時(shí)也要保證恰當(dāng)處理范圍邊界。
1.2 實(shí)戰(zhàn)示例
本節(jié)多數(shù)示例取自實(shí)際項(xiàng)目。表形式為一維數(shù)組、二維數(shù)組和結(jié)構(gòu)體數(shù)組;表內(nèi)容有數(shù)據(jù)、字符串和函數(shù)指針。基于表驅(qū)動(dòng)的思想,表形式和表內(nèi)容可衍生出豐富的組合。
1.2.1 字符統(tǒng)計(jì)
問(wèn)題:統(tǒng)計(jì)用戶輸入的一串?dāng)?shù)字中每個(gè)數(shù)字出現(xiàn)的次數(shù)。
普通解法主體代碼如下:
INT32UaDigitCharNum[10]={0};/*輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù)*/ INT32UdwStrLen=strlen(szDigits); INT32UdwStrIdx=0; for(;dwStrIdx
這種解法的缺點(diǎn)顯而易見,既不美觀也不靈活。其問(wèn)題關(guān)鍵在于未將數(shù)字字符與數(shù)組aDigitCharNum下標(biāo)直接關(guān)聯(lián)起來(lái)。
以下示出更簡(jiǎn)潔的實(shí)現(xiàn)方式:
for(;dwStrIdx
上述實(shí)現(xiàn)考慮到0也為數(shù)字字符。該解法也可擴(kuò)展至統(tǒng)計(jì)所有ASCII可見字符。
1.2.2 月天校驗(yàn)
問(wèn)題:對(duì)給定年份和月份的天數(shù)進(jìn)行校驗(yàn)(需區(qū)分平年和閏年)。
普通解法主體代碼如下:
switch(OnuTime.Month) { case1: case3: case5: case7: case8: case10: case12: if(OnuTime.Day>31||OnuTime.Day<1) ????????{ ????????????CtcOamLog(FUNCTION_Pon,"Don't?support?this?Day:?%d(1~31)!!! ",?OnuTime.Day); ????????????retcode?=?S_ERROR; ????????} ????????break; ????case?2: ????????if(((OnuTime.Year%4?==?0)?&&?(OnuTime.Year%100?!=?0))?||?(OnuTime.Year%400?==?0)) ????????{ ????????????if(OnuTime.Day>29||OnuTime.Day<1) ????????????{ ????????????????CtcOamLog(FUNCTION_Pon,"Don't?support?this?Day:?%d(1~29)!!! ",?OnuTime.Day); ????????????????retcode?=?S_ERROR; ????????????} ????????} ????????else ????????{ ????????????if(OnuTime.Day>28||OnuTime.Day<1) ????????????{ ????????????????CtcOamLog(FUNCTION_Pon,"Don't?support?this?Day:?%d(1~28)!!! ",?OnuTime.Day); ????????????????retcode?=?S_ERROR; ????????????} ????????} ????????break; ????case?4: ????case?6: ????case?9: ????case?11: ????????if(OnuTime.Day>30||OnuTime.Day<1) ????????{ ????????????CtcOamLog(FUNCTION_Pon,"Don't?support?this?Day:?%d(1~30)!!! ",?OnuTime.Day); ????????????retcode?=?S_ERROR; ????????} ????????break; ????default: ????????CtcOamLog(FUNCTION_Pon,"Don't?support?this?Month:?%d(1~12)!!! ",?OnuTime.Month); ????????retcode?=?S_ERROR; ????????break; }
以下示出更簡(jiǎn)潔的實(shí)現(xiàn)方式:
#defineMONTH_OF_YEAR12/*一年中的月份數(shù)*/ /*閏年:能被4整除且不能被100整除,或能被400整除*/ #defineIS_LEAP_YEAR(year)((((year)%4==0)&&((year)%100!=0))||((year)%400==0)) /*平年中的各月天數(shù),下標(biāo)對(duì)應(yīng)月份*/ INT8UaDayOfCommonMonth[MONTH_OF_YEAR]={31,28,31,30,31,30,31,31,30,31,30,31}; INT8UucMaxDay=0; if((OnuTime.Month==2)&&(IS_LEAP_YEAR(OnuTime.Year))) ucMaxDay=aDayOfCommonMonth[1]+1; else ucMaxDay=aDayOfCommonMonth[OnuTime.Month-1]; if((OnuTime.Day1)?||?(OnuTime.Day?>ucMaxDay) { CtcOamLog(FUNCTION_Pon,"Month%ddoesn'thavethisDay:%d(1~%d)!!! ", OnuTime.Month,OnuTime.Day,ucMaxDay); retcode=S_ERROR; }
1.2.3 名稱構(gòu)造
問(wèn)題:根據(jù)WAN接口承載的業(yè)務(wù)類型(Bitmap)構(gòu)造業(yè)務(wù)類型名稱字符串。
普通解法主體代碼如下:
voidSub_SetServerType(INT8U*ServerType,INT16Uwan_servertype) { if((wan_servertype&0x0001)==0x0001) { strcat(ServerType,"_INTERNET"); } if((wan_servertype&0x0002)==0x0002) { strcat(ServerType,"_TR069"); } if((wan_servertype&0x0004)==0x0004) { strcat(ServerType,"_VOIP"); } if((wan_servertype&0x0008)==0x0008) { strcat(ServerType,"_OTHER"); } }
以下示出C語(yǔ)言中更簡(jiǎn)潔的實(shí)現(xiàn)方式:
/*獲取var變量第bit位,編號(hào)從右至左*/ #defineGET_BIT(var,bit)(((var)>>(bit))&0x1) constCHAR*paSvrNames[]={"_INTERNET","_TR069","_VOIP","_OTHER"}; constINT8UucSvrNameNum=sizeof(paSvrNames)/sizeof(paSvrNames[0]); VOIDSetServerType(CHAR*pszSvrType,INT16UwSvrType) { INT8UucIdx=0; for(;ucIdx
新的實(shí)現(xiàn)將數(shù)據(jù)和邏輯分離,維護(hù)起來(lái)非常方便。只要邏輯(規(guī)則)不變,則唯一可能的改動(dòng)就是數(shù)據(jù)(paSvrNames)。
1.2.4 值名解析
問(wèn)題:根據(jù)枚舉變量取值輸出其對(duì)應(yīng)的字符串,如PORT_FE(1)輸出“Fe”。
//值名映射表結(jié)構(gòu)體定義,用于數(shù)值解析器typedefstruct{ INT32UdwElem;//待解析數(shù)值,通常為枚舉變量 CHAR*pszName;//指向數(shù)值所對(duì)應(yīng)解析名字符串的指針 }T_NAME_PARSER; /****************************************************************************** *函數(shù)名稱:NameParser *功能說(shuō)明:數(shù)值解析器,將給定數(shù)值轉(zhuǎn)換為對(duì)應(yīng)的具名字符串 *輸入參數(shù):VOID*pvMap:值名映射表數(shù)組,含T_NAME_PARSER結(jié)構(gòu)體類型元素 VOID指針允許用戶在保持成員數(shù)目和類型不變的前提下, 定制更有意義的結(jié)構(gòu)體名和/或成員名。 INT32UdwEntryNum:值名映射表數(shù)組條目數(shù) INT32UdwElem:待解析數(shù)值,通常為枚舉變量 INT8U*pszDefName:缺省具名字符串指針,可為空 *輸出參數(shù):NA *返回值:INT8U*:數(shù)值所對(duì)應(yīng)的具名字符串 當(dāng)無(wú)法解析給定數(shù)值時(shí),若pszDefName為空,則返回?cái)?shù)值對(duì)應(yīng)的16進(jìn)制格式 字符串;否則返回pszDefName。 ******************************************************************************/ INT8U*NameParser(VOID*pvMap,INT32UdwEntryNum,INT32UdwElem,INT8U*pszDefName) { CHECK_SINGLE_POINTER(pvMap,"NullPoniter"); INT32UdwEntryIdx=0; for(dwEntryIdx=0;dwEntryIdxdwElem) { returnptNameParser->pszName; } //ANSI標(biāo)準(zhǔn)禁止對(duì)void指針進(jìn)行算法操作;GNU標(biāo)準(zhǔn)則指定void*算法操作與char*一致。 //若考慮移植性,可將pvMap類型改為INT8U*,或定義INT8U*局部變量指向pvMap。 pvMap+=sizeof(T_NAME_PARSER); } if(NULL!=pszDefName) { returnpszDefName; } else { staticINT8UszName[12]={0};//Max:"0xFFFFFFFF" sprintf(szName,"0x%X",dwElem); returnszName; } } 以下給出NameParser的簡(jiǎn)單應(yīng)用示例: //UNI端口類型值名映射表結(jié)構(gòu)體定義 typedefstruct{ INT32UdwPortType; INT8U*pszPortName; }T_PORT_NAME; //UNI端口類型解析器 T_PORT_NAMEgUniNameMap[]={ {1,"Fe"}, {3,"Pots"}, {99,"Vuni"} }; constINT32UUNI_NAM_MAP_NUM=(INT32U)(sizeof(gUniNameMap)/sizeof(T_PORT_NAME)); VOIDNameParserTest(VOID) { INT8UucTestIndex=1; printf("[%s]Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("Unknown",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,0,"Unknown"))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("DefName",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,0,"DefName"))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("Fe",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,1,"Unknown"))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("Pots",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,3,"Unknown"))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("Vuni",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,99,NULL))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("Unknown",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,255,"Unknown"))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("0xABCD",NameParser(gUniNameMap,UNI_NAM_MAP_NUM,0xABCD,NULL))?"ERROR":"OK"); printf("[%s] Result:%s! ",__FUNCTION__,ucTestIndex++, strcmp("NullPoniter",NameParser(NULL,UNI_NAM_MAP_NUM,0xABCD,NULL))?"ERROR":"OK"); }
gUniNameMap在實(shí)際項(xiàng)目中有十余個(gè)條目,若采用邏輯鏈實(shí)現(xiàn)將非常冗長(zhǎng)。
1.2.5 取值映射
問(wèn)題:不同模塊間同一參數(shù)枚舉值取值可能有所差異,需要適配。
此處不再給出普通的switch…case或if…else if…else結(jié)構(gòu),而直接示出以下表驅(qū)動(dòng)實(shí)現(xiàn):
typedefstruct{ PORTSTATEloopMEState; PORTSTATEloopMIBState; }LOOPMAPSTRUCT; staticLOOPMAPSTRUCTs_CesLoop[]={ {NO_LOOP,e_ds1_looptype_noloop}, {PAYLOAD_LOOP,e_ds1_looptype_PayloadLoop}, {LINE_LOOP,e_ds1_looptype_LineLoop}, {PON_LOOP,e_ds1_looptype_OtherLoop}, {CES_LOOP,e_ds1_looptype_InwardLoop}}; PORTSTATEConvertLoopMEStateToMIBState(PORTSTATEvPortState) { INT32Unum=0,ii; num=ARRAY_NUM(s_CesLoop); for(ii=0;ii
相應(yīng)地,從loopMIBState映射到loopMEState需要定義一個(gè)ConvertLoopMIBStateToMEState函數(shù)。更進(jìn)一步,所有類似的一對(duì)一映射關(guān)系都必須如上的映射(轉(zhuǎn)換)函數(shù),相當(dāng)繁瑣。
事實(shí)上,從抽象層面看,該映射關(guān)系非常簡(jiǎn)單。提取共性后定義帶參數(shù)宏,如下所示:
/********************************************************** *功能描述:進(jìn)行二維數(shù)組映射表的一對(duì)一映射,用于參數(shù)適配 *參數(shù)說(shuō)明:map --二維數(shù)組映射表 elemSrc--映射源,即待映射的元素值 elemDest--映射源對(duì)應(yīng)的映射結(jié)果 direction --映射方向字節(jié),表示從數(shù)組哪列映射至哪列。 高4位對(duì)應(yīng)映射源列,低4位對(duì)應(yīng)映射結(jié)果列。 defaultVal--映射失敗時(shí)置映射結(jié)果為缺省值 *示例:ARRAY_MAPPER(gCesLoopMap, 3, ucLoop, 0x10, NO_LOOP); 則ucLoop=2(LINE_LOOP) **********************************************************/ #defineARRAY_MAPPER(map,elemSrc,elemDest,direction,defaultVal)do{ INT8UucMapIdx=0,ucMapNum=0; ucMapNum=sizeof(map)/sizeof(map[0]); for(ucMapIdx=0;ucMapIdx>4]) { elemDest=map[ucMapIdx][(direction)&0x0F]; break; } } if(ucMapIdx==ucMapNum) { elemDest=(defaultVal); } }while(0)
參數(shù)取值轉(zhuǎn)換時(shí)直接調(diào)用統(tǒng)一的映射器宏,如下:
staticINT8UgCesLoopMap[][2]={ {NO_LOOP,e_ds1_looptype_noloop}, {PAYLOAD_LOOP,e_ds1_looptype_PayloadLoop}, {LINE_LOOP,e_ds1_looptype_LineLoop}, {PON_LOOP,e_ds1_looptype_OtherLoop}, {CES_LOOP,e_ds1_looptype_InwardLoop}}; ARRAY_MAPPER(gCesLoopMap,tPara.dwParaVal[0],dwLoopConf,0x01,e_ds1_looptype_noloop); 另舉一例: #defineCES_DEFAULT_JITTERBUF(INT32U)2000/*默認(rèn)jitterbuf為2000us,而1幀=125us*/ #defineCES_JITTERBUF_STEP(INT32U)125/*jitterbuf步長(zhǎng)為125us,即1幀*/ #defineCES_DEFAULT_QUEUESIZE(INT32U)5 #defineCES_DEFAULT_MAX_QUEUESIZE(INT32U)7 #defineARRAY_NUM(array)(sizeof(array)/sizeof((array)[0]))/*數(shù)組元素個(gè)數(shù)*/ typedefstruct{ INT32UdwJitterBuffer; INT32UdwFramePerPkt; INT32UdwQueueSize; }QUEUE_SIZE_MAP; /*gCesQueueSizeMap也可以(JitterBuffer/FramePerPkt)值為索引,更加緊湊*/ staticQUEUE_SIZE_MAPgCesQueueSizeMap[]={ {1,1,1},{1,2,1},{2,1,2},{2,2,1}, {3,1,3},{3,2,1},{4,1,3},{4,2,1}, {5,1,4},{5,2,3},{6,1,4},{6,2,3}, {7,1,4},{7,2,3},{8,1,4},{8,2,3}, {9,1,5},{9,2,4},{10,1,5},{10,2,4}, {11,1,5},{11,2,4},{12,1,5},{12,2,4}, {13,1,5},{13,2,4},{14,1,5},{14,2,4}, {15,1,5},{15,2,4},{16,1,5},{16,2,4}, {17,1,6},{17,2,5},{18,1,6},{18,2,5}, {19,1,6},{19,2,5},{20,1,6},{20,2,5}, {21,1,6},{21,2,5},{22,1,6},{22,2,5}, {23,1,6},{23,2,5},{24,1,6},{24,2,5}, {25,1,6},{25,2,5},{26,1,6},{26,2,5}, {27,1,6},{27,2,5},{28,1,6},{28,2,5}, {29,1,6},{29,2,5},{30,1,6},{30,2,5}, {31,1,6},{31,2,5},{32,1,6},{32,2,5}}; /********************************************************** *函數(shù)名稱:CalcQueueSize *功能描述:根據(jù)JitterBuffer和FramePerPkt計(jì)算QueueSize *注意事項(xiàng):配置的最大緩存深度 *=2*JitterBuffer/FramePerPkt *=2*NPacket=2^QueueSize *JitterBuffer為125us幀速率的倍數(shù), *FramePerPkt為每個(gè)分組的幀數(shù), * QueueSize向上取整,最大為7。 **********************************************************/ INT32UCalcQueueSize(INT32UdwJitterBuffer,INT32UdwFramePerPkt) { INT8UucIdx=0,ucNum=0; //本函數(shù)暫時(shí)僅考慮E1 ucNum=ARRAY_NUM(gCesQueueSizeMap); for(ucIdx=0;ucIdx
1.2.6 版本控制
問(wèn)題:控制OLT與ONU之間的版本協(xié)商。ONU本地設(shè)置三比特控制字,其中bit2(MSB)~bit0(LSB)分別對(duì)應(yīng)0x21、0x30和0xAA版本號(hào);且bitX為0表示上報(bào)對(duì)應(yīng)版本號(hào),bitX為1表示不上報(bào)對(duì)應(yīng)版本號(hào)。其他版本號(hào)如0x20、0x13和0x1必須上報(bào),即不受控制。
最初的實(shí)現(xiàn)采用if…else if…else結(jié)構(gòu),代碼非常冗長(zhǎng),如下:
pstSendTlv->ucLength=0x1f; if(gOamCtrlCode==0) { vosMemCpy(pstSendTlv->aucVersionList,ctc_oui,3); pstSendTlv->aucVersionList[3]=0x30; vosMemCpy(&(pstSendTlv->aucVersionList[4]),ctc_oui,3); pstSendTlv->aucVersionList[7]=0x21; vosMemCpy(&(pstSendTlv->aucVersionList[8]),ctc_oui,3); pstSendTlv->aucVersionList[11]=0x20; vosMemCpy(&(pstSendTlv->aucVersionList[12]),ctc_oui,3); pstSendTlv->aucVersionList[15]=0x13; vosMemCpy(&(pstSendTlv->aucVersionList[16]),ctc_oui,3); pstSendTlv->aucVersionList[19]=0x01; vosMemCpy(&(pstSendTlv->aucVersionList[20]),ctc_oui,3); pstSendTlv->aucVersionList[23]=0xaa; } elseif(gOamCtrlCode==1) { vosMemCpy(pstSendTlv->aucVersionList,ctc_oui,3); pstSendTlv->aucVersionList[3]=0x30; vosMemCpy(&(pstSendTlv->aucVersionList[4]),ctc_oui,3); pstSendTlv->aucVersionList[7]=0x21; vosMemCpy(&(pstSendTlv->aucVersionList[8]),ctc_oui,3); pstSendTlv->aucVersionList[11]=0x20; vosMemCpy(&(pstSendTlv->aucVersionList[12]),ctc_oui,3); pstSendTlv->aucVersionList[15]=0x13; vosMemCpy(&(pstSendTlv->aucVersionList[16]),ctc_oui,3); pstSendTlv->aucVersionList[19]=0x01; } //此處省略gOamCtrlCode==2~6的處理代碼 elseif(gOamCtrlCode==7) { vosMemCpy(&(pstSendTlv->aucVersionList),ctc_oui,3); pstSendTlv->aucVersionList[3]=0x20; vosMemCpy(&(pstSendTlv->aucVersionList[4]),ctc_oui,3); pstSendTlv->aucVersionList[7]=0x13; vosMemCpy(&(pstSendTlv->aucVersionList[8]),ctc_oui,3); pstSendTlv->aucVersionList[11]=0x01; } 以下示出C語(yǔ)言中更簡(jiǎn)潔的實(shí)現(xiàn)方式(基于二維數(shù)組): /********************************************************************** *版本控制字?jǐn)?shù)組定義 * gOamCtrlCode: Bitmap控制字。Bit-X為0時(shí)上報(bào)對(duì)應(yīng)版本,Bit-X為1時(shí)屏蔽對(duì)應(yīng)版本。 * CTRL_VERS_NUM:可控版本個(gè)數(shù)。 * CTRL_CODE_NUM:控制字個(gè)數(shù)。與CTRL_VERS_NUM有關(guān)。 * gOamVerCtrlMap:版本控制字?jǐn)?shù)組。行對(duì)應(yīng)控制字,列對(duì)應(yīng)可控版本。 元素值為0時(shí)不上報(bào)對(duì)應(yīng)版本,元素值非0時(shí)上報(bào)該元素值。 * Note:該數(shù)組旨在實(shí)現(xiàn)“數(shù)據(jù)與控制隔離”。后續(xù)若要新增可控版本,只需修改 --CTRL_VERS_NUM --gOamVerCtrlMap新增行(控制字) --gOamVerCtrlMap新增列(可控版本) **********************************************************************/ #defineCTRL_VERS_NUM3 #defineCTRL_CODE_NUM(1<aucVersionList[index],ctc_oui,3); index+=3; pstSendTlv->aucVersionList[index++]=gOamVerCtrlMap[gOamCtrlCode][verIdx]; } } vosMemCpy(&pstSendTlv->aucVersionList[index],ctc_oui,3); index+=3; pstSendTlv->aucVersionList[index++]=0x20; vosMemCpy(&pstSendTlv->aucVersionList[index],ctc_oui,3); index+=3; pstSendTlv->aucVersionList[index++]=0x13; vosMemCpy(&pstSendTlv->aucVersionList[index],ctc_oui,3); index+=3; pstSendTlv->aucVersionList[index++]=0x01; pstSendTlv->ucLength=INFO_TYPE_VERS_LEN+index;
1.2.7 消息處理
問(wèn)題:終端輸入不同的打印命令,調(diào)用相應(yīng)的打印函數(shù),以控制不同級(jí)別的打印。這是一段消息(事件)驅(qū)動(dòng)程序。本模塊接收其他模塊(如串口驅(qū)動(dòng))發(fā)送的消息,根據(jù)消息中的打印級(jí)別字符串和開關(guān)模式,調(diào)用不同函數(shù)進(jìn)行處理。常見的實(shí)現(xiàn)方法如下:
voidlogall(void) { g_log_control[0]=0xFFFFFFFF; } voidnoanylog(void) { g_log_control[0]=0; } voidlogOam(void) { g_log_control[0]|=(0x01<
以下示出C語(yǔ)言中更簡(jiǎn)潔的實(shí)現(xiàn)方式:
typedefstruct{ OAM_LOG_OFF=(INT8U)0, OAM_LOG_ON=(INT8U)1 }E_OAM_LOG_MODE; typedefFUNC_STATUS(*OamLogHandler)(VOID); typedefstruct{ CHAR*pszLogCls;/*打印級(jí)別*/ E_OAM_LOG_MODEeLogMode;/*打印模式*/ OamLogHandlerfnLogHandler;/*打印函數(shù)*/ }T_OAM_LOG_MAP; T_OAM_LOG_MAPgOamLogMap[]={ {"all",OAM_LOG_OFF,noanylog}, {"oam",OAM_LOG_OFF,nologOam}, //...... {"version",OAM_LOG_OFF,nologVersion}, {"all",OAM_LOG_ON,logall}, {"oam",OAM_LOG_ON,logOam}, //...... {"version",OAM_LOG_ON,logVersion} }; INT32UgOamLogMapNum=sizeof(gOamLogMap)/sizeof(T_OAM_LOG_MAP); VOIDlogExec(CHAR*pszName,INT8UucSwitch) { INT8UucIdx=0; for(;ucIdx
這種表驅(qū)動(dòng)消息處理實(shí)現(xiàn)的優(yōu)點(diǎn)如下:
1. 增強(qiáng)可讀性,消息如何處理從表中一目了然。
2. 增強(qiáng)可擴(kuò)展性。更容易修改,要增加新的消息,只要修改數(shù)據(jù)即可,不需要修改流程。
3. 降低復(fù)雜度。通過(guò)把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來(lái),從而達(dá)到控制復(fù)雜度的目標(biāo)。4. 主干清晰,代碼重用。
若各索引為順序枚舉值,則建立多維數(shù)組(每維對(duì)應(yīng)一個(gè)索引),根據(jù)下標(biāo)直接定位到處理函數(shù),效率會(huì)更高。
注意,考慮到本節(jié)實(shí)例中l(wèi)ogOam/logPon或nologOam/nologPon等函數(shù)本質(zhì)上是基于打印級(jí)別的比特操作,因此可進(jìn)一步簡(jiǎn)化。以下例舉其相似實(shí)現(xiàn):
/*日志控制類型定義*/ typedefenum { LOG_NORM=0,/*未分類日志,可用于通用日志*/ LOG_FRM,/*Frame,OMCI幀日志*/ LOG_PON,/*Pon,光鏈路相關(guān)日志*/ LOG_ETH,/*Ethernet,Layer2以太網(wǎng)日志*/ LOG_NET,/*Internet,Layer3網(wǎng)絡(luò)日志*/ LOG_MULT,/*Multicast,組播日志*/ LOG_QOS,/*QOS,流量日志*/ LOG_CES,/*Ces,TDM電路仿真日志*/ LOG_VOIP,/*Voip,語(yǔ)音日志*/ LOG_ALM,/*Alarm,告警日志*/ LOG_PERF,/*Performance,性能統(tǒng)計(jì)日志*/ LOG_VER,/*Version,軟件升級(jí)日志*/ LOG_XDSL,/*xDsl日志*/ LOG_DB,/*數(shù)據(jù)庫(kù)操作日志*/ //新日志類型在此處擴(kuò)展,共支持32種日志類型 LOG_ALL=UINT_MAX/*所有日志類型*/ }E_LOG_TYPE; /***************************************************************************** *變量名稱:gOmciLogCtrl *作用描述:OMCI日志控制字,BitMap格式(比特編號(hào)從LSB至MSB依次為Bit0->BitN)。 *Bit0~N分別對(duì)應(yīng)E_LOG_TYPE各枚舉值(除LOG_ALL外)。 *BitX為0時(shí)關(guān)閉日志類型對(duì)應(yīng)的日志功能,BitX為1時(shí)則予以打開。 *變量范圍:該變量為四字節(jié)整型靜態(tài)全局變量,即支持32種日志類型。 *訪問(wèn)說(shuō)明:通過(guò)GetOmciLogCtrl/SetOmciLogCtrl/OmciLogCtrl函數(shù)訪問(wèn)/設(shè)置控制字。 *****************************************************************************/ staticINT32UgOmciLogCtrl=0; //日志類型字符串?dāng)?shù)組,下標(biāo)為各字符串所對(duì)應(yīng)的日志類型枚舉值。 staticconstINT8U*paLogTypeName[]={ "Norm","Frame","Pon","Ethernet","Internet", "Multicast","Qos","Ces","Voip","Alarm", "Performance","Version","Xdsl","Db" }; staticconstINT8UucLogTypeNameNum=sizeof(paLogTypeName)/sizeof(paLogTypeName[0]); staticVOIDSetGlobalLogCtrl(E_LOG_TYPEeLogType,INT8UucLogSwitch) { if(LOG_ON==ucLogSwitch) gOmciLogCtrl=LOG_ALL; else gOmciLogCtrl=0; } staticVOIDSetSpecificLogCtrl(E_LOG_TYPEeLogType,INT8UucLogSwitch) { if(LOG_ON==ucLogSwitch) SET_BIT(gOmciLogCtrl,eLogType); else CLR_BIT(gOmciLogCtrl,eLogType); } VOIDOmciLogCtrl(CHAR*pszLogType,INT8UucLogSwitch) { if(0==strncasecmp(pszLogType,"All",LOG_TYPE_CMP_LEN)) { SetGlobalLogCtrl(LOG_ALL,ucLogSwitch); return; } INT8UucIdx=0; for(ucIdx=0;ucIdx
2. 編程思想
表驅(qū)動(dòng)法屬于數(shù)據(jù)驅(qū)動(dòng)編程的一種,其核心思想在《Unix編程藝術(shù)》和《代碼大全2》中均有闡述。兩者均認(rèn)為人類閱讀復(fù)雜數(shù)據(jù)結(jié)構(gòu)遠(yuǎn)比復(fù)雜的控制流程容易,即相對(duì)于程序邏輯,人類更擅長(zhǎng)于處理數(shù)據(jù)。
本節(jié)將由Unix設(shè)計(jì)原則中的“分離原則”和“表示原則”展開。
分離原則:策略同機(jī)制分離,接口同引擎分離
機(jī)制即提供的功能;策略即如何使用功能。
策略的變化要遠(yuǎn)遠(yuǎn)快于機(jī)制的變化。將兩者分離,可以使機(jī)制相對(duì)保持穩(wěn)定,而同時(shí)支持策略的變化。
代碼大全中提到“隔離變化”的概念,以及設(shè)計(jì)模式中提到的將易變化的部分和不易變化的部分分離也是這個(gè)思路。
表示原則:把知識(shí)疊入數(shù)據(jù)以求邏輯質(zhì)樸而健壯
即使最簡(jiǎn)單的程序邏輯讓人類來(lái)驗(yàn)證也很困難,但就算是很復(fù)雜的數(shù)據(jù),對(duì)人類來(lái)說(shuō),還是相對(duì)容易推導(dǎo)和建模的。數(shù)據(jù)比編程邏輯更容易駕馭。在復(fù)雜數(shù)據(jù)和復(fù)雜代碼中選擇,寧可選擇前者。更進(jìn)一步,在設(shè)計(jì)中,應(yīng)該主動(dòng)將代碼的復(fù)雜度轉(zhuǎn)移到數(shù)據(jù)中去(參考“版本控制”)。
在“消息處理”示例中,每個(gè)消息處理的邏輯不變,但消息可能是變化的。將容易變化的消息和不容易變化的查找邏輯分離,即“隔離變化”。此外,該例也體現(xiàn)消息內(nèi)部的處理邏輯(機(jī)制)和不同的消息處理(策略)分離。
數(shù)據(jù)驅(qū)動(dòng)編程可以應(yīng)用于:
1.函數(shù)級(jí)設(shè)計(jì),如本文示例。2.程序級(jí)設(shè)計(jì),如用表驅(qū)動(dòng)法實(shí)現(xiàn)狀態(tài)機(jī)。3.系統(tǒng)級(jí)設(shè)計(jì),如DSL。
注意,數(shù)據(jù)驅(qū)動(dòng)編程不是全新的編程模型,只是一種設(shè)計(jì)思路,在Unix/Linux開源社區(qū)應(yīng)用很多。數(shù)據(jù)驅(qū)動(dòng)編程中,數(shù)據(jù)不但表示某個(gè)對(duì)象的狀態(tài),實(shí)際上還定義程序的流程,這點(diǎn)不同于面向?qū)ο笤O(shè)計(jì)中的數(shù)據(jù)“封裝”。
3. 附錄
3.1 網(wǎng)友觀點(diǎn)
Booch的《面向?qū)ο蠓治雠c設(shè)計(jì)》一書中,提到所有的程序設(shè)計(jì)語(yǔ)言大概有3個(gè)源流:結(jié)構(gòu)化編程;面向?qū)ο缶幊?;?shù)據(jù)驅(qū)動(dòng)編程。
我認(rèn)為數(shù)據(jù)驅(qū)動(dòng)編程的本質(zhì)是“參數(shù)化抽象”的思想,不同于OO的“規(guī)范化抽象”的思想。
數(shù)據(jù)驅(qū)動(dòng)編程在網(wǎng)絡(luò)游戲開發(fā)過(guò)程中很常用,但是少有人專門提到這個(gè)詞。
數(shù)據(jù)驅(qū)動(dòng)編程有很多名字:元編程,解釋器/虛擬機(jī),LOP/微語(yǔ)言/DSL等。包括聲明式編程、標(biāo)記語(yǔ)言、甚至所見即所得的拖放控件,都算是數(shù)據(jù)驅(qū)動(dòng)編程的一種吧。
數(shù)據(jù)驅(qū)動(dòng)編程可以幫助處理復(fù)雜性,和結(jié)構(gòu)化編程、OO 均可相容。(正交的角度)將變和不變的部分分離,策略和機(jī)制分離,由此聯(lián)想到的還有:(數(shù)據(jù)和代碼的分離,微語(yǔ)言和解釋器的分離,被生成代碼和代碼生成器的分離);更近一步:(微內(nèi)核插件式體系結(jié)構(gòu))
元編程應(yīng)該說(shuō)是更加泛化的數(shù)據(jù)驅(qū)動(dòng)編程,元編程不是新加入一個(gè)間接層,而是退居一步,使得當(dāng)前的層變成一個(gè)間接層。元編程分為靜態(tài)元編程(編譯時(shí))和動(dòng)態(tài)元編程(運(yùn)行時(shí)),靜態(tài)元編程本質(zhì)上是一種 代碼生成技術(shù)或者編譯器技術(shù);動(dòng)態(tài)元編程一般通過(guò)解釋器(或虛擬機(jī))加以實(shí)現(xiàn)。
數(shù)據(jù)驅(qū)動(dòng)編程當(dāng)然也不應(yīng)該說(shuō)是“反抽象的”,但的確與“OO抽象”的思維方式是迥然不同,涇渭分明的,如TAOUP一書中所述:“在Unix的模塊化傳統(tǒng)和圍繞OO語(yǔ)言發(fā)展起來(lái)的使用模式之間,存在著緊張的對(duì)立關(guān)系”應(yīng)該說(shuō)數(shù)據(jù)驅(qū)動(dòng)編程的思路與結(jié)構(gòu)化編程和OO是正交的,更類似一種“跳出三界外,不在五行中”的做法。
編程和人的關(guān)系
人類心智的限制,一切的背后都有人的因素作為依據(jù):
a.人同時(shí)關(guān)注的信息數(shù)量:7+-2 (所以要分模塊)
b.人接收一組新信息的平均時(shí)間5s (所以要簡(jiǎn)單,系統(tǒng)總的模塊數(shù)不要太多)
c.人思維的直觀性(人的視覺(jué)能力和模糊思維能力),這意味這兩點(diǎn):A “直”——更善于思考自己能直接接觸把玩的東西;(所以要“淺平透”、使用具象的設(shè)計(jì),要盡量代碼中只有順直的流程),B “觀”——更善于觀圖而不是推算邏輯;(所以要表驅(qū)動(dòng)法,數(shù)據(jù)驅(qū)動(dòng)編程,要UML,要可視化編程——當(dāng)然MDA是太理想化了)
d.人不能持續(xù)集中注意力(人在一定的代碼行數(shù)中產(chǎn)生的bug數(shù)量的比例是一定的,所以語(yǔ)言有具有表現(xiàn)力,要體現(xiàn)表達(dá)的經(jīng)濟(jì)性)所以要機(jī)制與策略分離,要數(shù)據(jù)和代碼分離(數(shù)據(jù)驅(qū)動(dòng)編程),要微語(yǔ)言,要DSL,要LOP……e.人是有創(chuàng)造欲,有現(xiàn)實(shí)利益心的(只要偶可能總是不夠遵從規(guī)范,或想創(chuàng)造規(guī)范謀利——只要成本能承受,在硬件領(lǐng)域就不行)
另外,開一個(gè)有意思的玩笑,Unix編程藝術(shù)藝術(shù)的英文縮寫為TAOUP,我覺(jué)得可以理解為UP之TAO——向上拋出之道——將復(fù)雜的易變的邏輯作為數(shù)據(jù)或更高層代碼拋給上層!
3.2 函數(shù)指針
“消息處理”一節(jié)示例中的函數(shù)指針有點(diǎn)插件結(jié)構(gòu)的味道??蓪?duì)這些插件進(jìn)行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對(duì)事件處理函數(shù)的查找又是隔離的(隔離變化)。
函數(shù)指針?lè)浅S杏?,但使用時(shí)需注意其缺陷:無(wú)法檢查參數(shù)(parameter)和返回值(return value)的類型。因?yàn)楹瘮?shù)已經(jīng)退化成指針,而指針不攜帶這些類型信息。缺少類型檢查,當(dāng)參數(shù)或返回值不一致時(shí),可能會(huì)造成嚴(yán)重的錯(cuò)誤。
例如,定義三個(gè)函數(shù),分別具有兩個(gè)參數(shù):
intmax(intx,inty){returnx>y?x:y;} intmin(intx,inty){returnx
審核編輯:劉清
-
C語(yǔ)言
+關(guān)注
關(guān)注
180文章
7610瀏覽量
137232 -
編程語(yǔ)言
+關(guān)注
關(guān)注
10文章
1947瀏覽量
34845 -
WAN接口
+關(guān)注
關(guān)注
0文章
10瀏覽量
6838
原文標(biāo)題:C語(yǔ)言的驅(qū)動(dòng)法編程詳細(xì)解析(超多代碼案例)
文章出處:【微信號(hào):mcu168,微信公眾號(hào):硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論