數(shù)據(jù)壓倒一切。如果選擇了正確的數(shù)據(jù)結(jié)構(gòu)并把一切組織的井井有條,正確的算法就不言自明。編程的核心是數(shù)據(jù)結(jié)構(gòu),而不是算法?!猂ob Pike
說明
本文基于這樣的認(rèn)識(shí):數(shù)據(jù)是易變的,邏輯是穩(wěn)定的。 本文例舉的編程實(shí)現(xiàn)多為代碼片段,但不影響描述的完整性。 本文例舉的編程雖然基于C語言,但其編程思想也適用于其他語言。 此外,本文不涉及語言相關(guān)的運(yùn)行效率討論。
概念提出
所謂表驅(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)的頁數(shù)。相比一頁一頁地順序翻字典查字,部首檢字法效率極高。
具體到編程方面,在數(shù)據(jù)不多時(shí)可用邏輯判斷語句(if…else或switch…case)來獲取值;但隨著數(shù)據(jù)的增多,邏輯語句會(huì)越來越長(zhǎng),此時(shí)表驅(qū)動(dòng)法的優(yōu)勢(shì)就開始顯現(xiàn)。
例如,用36進(jìn)制(A表示10,B表示11,…)表示更大的數(shù)字,邏輯判斷語句如下:
if(ucNum?10) { ????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)潔。如:
CHAR?aNumChars[]?=?{'0',?'1',?'2',?/*3~9*/'A',?'B',?'C',?/*D~Y*/'Z'}; CHAR?ucNumChar?=?aNumChars[ucNum?%?sizeof(aNumChars)];
像這樣直接將變量當(dāng)作下數(shù)組下標(biāo)來讀取數(shù)值的方法就是直接查表法。
注意,如果熟悉字符串操作,則上述寫法可以更簡(jiǎn)潔:
CHAR?ucNumChar?=?"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[ucNum];
使用表驅(qū)動(dòng)法時(shí)需要關(guān)注兩個(gè)問題:一是如何查表,從表中讀取正確的數(shù)據(jù);二是表里存放什么,如數(shù)值或函數(shù)指針。前者參見1.1節(jié)“查表方式”內(nèi)容,后者參見1.2節(jié)“實(shí)戰(zhàn)示例”內(nèi)容。
查表方式
常用的查表方式有直接查找、索引查找和分段查找等。
直接查找
即直接通過數(shù)組下標(biāo)獲取到數(shù)據(jù)。如果熟悉哈希表的話,可以很容易看出這種查表方式就是哈希表的直接訪問法。
如獲取星期名稱,邏輯判斷語句如下:
if(0?==?ucDay) { ????pszDayName?=?"Sunday"; } else?if(1?==?ucDay) { ????pszDayName?=?"Monday"; } //...?... else?if(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)法適用于無需有序遍歷數(shù)據(jù),且數(shù)據(jù)量大小可提前預(yù)測(cè)的情況。
對(duì)于過于復(fù)雜和龐大的判斷,可將數(shù)據(jù)存為文件,需要時(shí)加載文件初始化數(shù)組,從而在不修改程序的情況下調(diào)整里面的數(shù)值。
有時(shí),訪問之前需要先進(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)。
索引查找
有時(shí)通過一次鍵值轉(zhuǎn)換,依然無法把數(shù)據(jù)(如英文單詞等)轉(zhuǎn)為鍵值。此時(shí)可將轉(zhuǎn)換的對(duì)應(yīng)關(guān)系寫到一個(gè)索引表里,即索引訪問。
如現(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位的鍵值,可能過于復(fù)雜或沒有規(guī)律,最合適的方法是建立一個(gè)保存該轉(zhuǎn)換關(guān)系的索引表。采用索引訪問既節(jié)省內(nèi)存,又方便維護(hù)。比如索引A表示通過名稱訪問,索引B表示通過編號(hào)訪問。
分段查找
通過確定數(shù)據(jù)所處的范圍確定分類(下標(biāo))。有的數(shù)據(jù)可分成若干區(qū)間,即具有階梯性,如分?jǐn)?shù)等級(jí)。此時(shí)可將每個(gè)區(qū)間的上限(或下限)存到一個(gè)表中,將對(duì)應(yīng)的值存到另一表中,通過第一個(gè)表確定所處的區(qū)段,再由區(qū)段下標(biāo)在第二個(gè)表里讀取相應(yīng)數(shù)值。注意要留意端點(diǎn),可用二分法查找,另外可考慮通過索引方法來代替。
如根據(jù)分?jǐn)?shù)查績(jī)效等級(jí):
#define?MAX_GRADE_LEVEL???(INT8U)5 DOUBLE?aRangeLimit[MAX_GRADE_LEVEL]?=?{50.0,?60.0,?70.0,?80.0,?100.0}; CHAR?*paGrades[MAX_GRADE_LEVEL]?=?{"Fail",?"Pass",?"Credit",?"Distinction",?"High?Distinction"}; static?CHAR*?EvaluateGrade(DOUBLE?dScore) { ????INT8U?ucLevel?=?0; ????for(;?ucLevel?上述兩張表(數(shù)組)也可合并為一張表(結(jié)構(gòu)體數(shù)組),如下所示:
typedef?struct{ ????DOUBLE??aRangeLimit; ????CHAR????*pszGrade; }T_GRADE_MAP; T_GRADE_MAP?gGradeMap[MAX_GRADE_LEVEL]?=?{ ????{50.0,??????????????"Fail"}, ????{60.0,??????????????"Pass"}, ????{70.0,??????????????"Credit"}, ????{80.0,??????????????"Distinction"}, ????{100.0,?????????????"High?Distinction"} }; static?CHAR*?EvaluateGrade(DOUBLE?dScore) { ????INT8U?ucLevel?=?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ò)誤地用“<”來代替“<=”。要保證循環(huán)在找出屬于最高一級(jí)范圍內(nèi)的值后恰當(dāng)?shù)亟Y(jié)束,同時(shí)也要保證恰當(dāng)處理范圍邊界。
實(shí)戰(zhàn)示例
本節(jié)多數(shù)示例取自實(shí)際項(xiàng)目。表形式為一維數(shù)組、二維數(shù)組和結(jié)構(gòu)體數(shù)組;表內(nèi)容有數(shù)據(jù)、字符串和函數(shù)指針?;诒眚?qū)動(dòng)的思想,表形式和表內(nèi)容可衍生出豐富的組合。
字符統(tǒng)計(jì)
問題:統(tǒng)計(jì)用戶輸入的一串?dāng)?shù)字中每個(gè)數(shù)字出現(xiàn)的次數(shù)。
普通解法主體代碼如下:
INT32U?aDigitCharNum[10]?=?{0};?/*?輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù)?*/ INT32U?dwStrLen?=?strlen(szDigits); INT32U?dwStrIdx?=?0; for(;?dwStrIdx?這種解法的缺點(diǎn)顯而易見,既不美觀也不靈活。其問題關(guān)鍵在于未將數(shù)字字符與數(shù)組aDigitCharNum下標(biāo)直接關(guān)聯(lián)起來。
以下示出更簡(jiǎn)潔的實(shí)現(xiàn)方式:
for(;?dwStrIdx?上述實(shí)現(xiàn)考慮到0也為數(shù)字字符。該解法也可擴(kuò)展至統(tǒng)計(jì)所有ASCII可見字符。
月天校驗(yàn)
問題:對(duì)給定年份和月份的天數(shù)進(jìn)行校驗(yàn)(需區(qū)分平年和閏年)。
普通解法主體代碼如下:
switch(OnuTime.Month)
{ ????case?1: ????case?3: ????case?5: ????case?7: ????case?8: ????case?10: ????case?12: ????????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)方式:
#define?MONTH_OF_YEAR?12????/*?一年中的月份數(shù)?*/ /*?閏年:能被4整除且不能被100整除,或能被400整除?*/ #define?IS_LEAP_YEAR(year)?((((year)?%?4?==?0)?&&?((year)?%?100?!=?0))?||?((year)?%?400?==?0)) /*?平年中的各月天數(shù),下標(biāo)對(duì)應(yīng)月份?*/ INT8U?aDayOfCommonMonth[MONTH_OF_YEAR]?=?{31,?28,?31,?30,?31,?30,?31,?31,?30,?31,?30,?31}; INT8U?ucMaxDay?=?0; if((OnuTime.Month?==?2)?&&?(IS_LEAP_YEAR(OnuTime.Year))) ????ucMaxDay?=?aDayOfCommonMonth[1]?+?1; else ????ucMaxDay?=?aDayOfCommonMonth[OnuTime.Month-1]; if((OnuTime.Day?1)?||?(OnuTime.Day?>?ucMaxDay) { ????CtcOamLog(FUNCTION_Pon,"Month?%d?doesn't?have?this?Day:?%d(1~%d)!!! ", ??????????????OnuTime.Month,?OnuTime.Day,?ucMaxDay); ????retcode?=?S_ERROR; }名稱構(gòu)造
問題:根據(jù)WAN接口承載的業(yè)務(wù)類型(Bitmap)構(gòu)造業(yè)務(wù)類型名稱字符串。
普通解法主體代碼如下:
void?Sub_SetServerType(INT8U?*ServerType,?INT16U?wan_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語言中更簡(jiǎn)潔的實(shí)現(xiàn)方式:
/*?獲取var變量第bit位,編號(hào)從右至左?*/ #define??GET_BIT(var,?bit)???(((var)?>>?(bit))?&?0x1) const?CHAR*?paSvrNames[]?=?{"_INTERNET",?"_TR069",?"_VOIP",?"_OTHER"}; const?INT8U?ucSvrNameNum?=?sizeof(paSvrNames)?/?sizeof(paSvrNames[0]); VOID?SetServerType(CHAR?*pszSvrType,?INT16U?wSvrType) { ????INT8U?ucIdx?=?0; ????for(;?ucIdx?新的實(shí)現(xiàn)將數(shù)據(jù)和邏輯分離,維護(hù)起來非常方便。只要邏輯(規(guī)則)不變,則唯一可能的改動(dòng)就是數(shù)據(jù)(paSvrNames)。
值名解析
問題:根據(jù)枚舉變量取值輸出其對(duì)應(yīng)的字符串,如PORT_FE(1)輸出“Fe”。
//值名映射表結(jié)構(gòu)體定義,用于數(shù)值解析器 typedef?struct{ ????INT32U?dwElem;????//待解析數(shù)值,通常為枚舉變量 ????CHAR*??pszName;???//指向數(shù)值所對(duì)應(yīng)解析名字符串的指針 }T_NAME_PARSER; /****************************************************************************** *?函數(shù)名稱:??NameParser *?功能說明:??數(shù)值解析器,將給定數(shù)值轉(zhuǎn)換為對(duì)應(yīng)的具名字符串 *?輸入參數(shù):??VOID?*pvMap???????:值名映射表數(shù)組,含T_NAME_PARSER結(jié)構(gòu)體類型元素 ????????????????????????????????VOID指針允許用戶在保持成員數(shù)目和類型不變的前提下, ????????????????????????????????定制更有意義的結(jié)構(gòu)體名和/或成員名。 ?????????????INT32U?dwEntryNum?:值名映射表數(shù)組條目數(shù) ?????????????INT32U?dwElem?????:待解析數(shù)值,通常為枚舉變量 ?????????????INT8U*?pszDefName?:缺省具名字符串指針,可為空 *?輸出參數(shù):??NA *?返回值??:??INT8U?*:?數(shù)值所對(duì)應(yīng)的具名字符串 ?????????????當(dāng)無法解析給定數(shù)值時(shí),若pszDefName為空,則返回?cái)?shù)值對(duì)應(yīng)的16進(jìn)制格式 ?????????????字符串;否則返回pszDefName。 ******************************************************************************/ INT8U?*NameParser(VOID?*pvMap,?INT32U?dwEntryNum,?INT32U?dwElem,?INT8U*?pszDefName) { ????CHECK_SINGLE_POINTER(pvMap,?"NullPoniter"); ????INT32U?dwEntryIdx?=?0; ????for(dwEntryIdx?=?0;?dwEntryIdx?dwElem) ????????{ ????????????return?ptNameParser->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) ????{ ????????return?pszDefName; ????} ????else ????{ ????????static?INT8U?szName[12]?=?{0};?//Max:"0xFFFFFFFF" ????????sprintf(szName,?"0x%X",?dwElem); ????????return?szName; ????} }以下給出NameParser的簡(jiǎn)單應(yīng)用示例:
//UNI端口類型值名映射表結(jié)構(gòu)體定義 typedef?struct{ ????INT32U?dwPortType; ????INT8U*?pszPortName; }T_PORT_NAME; //UNI端口類型解析器 T_PORT_NAME?gUniNameMap[]?=?{ ????{1,??????"Fe"}, ????{3,??????"Pots"}, ????{99,?????"Vuni"} }; const?INT32U?UNI_NAM_MAP_NUM?=?(INT32U)(sizeof(gUniNameMap)/sizeof(T_PORT_NAME)); VOID?NameParserTest(VOID) { ????INT8U?ucTestIndex?=?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)。
取值映射
問題:不同模塊間同一參數(shù)枚舉值取值可能有所差異,需要適配。
此處不再給出普通的switch…case或if…else if…else結(jié)構(gòu),而直接示出以下表驅(qū)動(dòng)實(shí)現(xiàn):
typedef?struct{ ????PORTSTATE?loopMEState; ????PORTSTATE?loopMIBState; }LOOPMAPSTRUCT; static?LOOPMAPSTRUCT?s_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}}; PORTSTATE?ConvertLoopMEStateToMIBState(PORTSTATE?vPortState) { ????INT32U?num?=?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ù)說明: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) **********************************************************/ #define?ARRAY_MAPPER(map,?elemSrc,?elemDest,?direction,?defaultVal)?do{ ????INT8U?ucMapIdx?=?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)一的映射器宏,如下:
static?INT8U?gCesLoopMap[][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);另舉一例:
#define??CES_DEFAULT_JITTERBUF????????(INT32U)2000???/*?默認(rèn)jitterbuf為2000us,而1幀=125us?*/ #define??CES_JITTERBUF_STEP???????????(INT32U)125????/*?jitterbuf步長(zhǎng)為125us,即1幀?*/ #define??CES_DEFAULT_QUEUESIZE????????(INT32U)5 #define??CES_DEFAULT_MAX_QUEUESIZE????(INT32U)7 #define??ARRAY_NUM(array)?????????????(sizeof(array)?/?sizeof((array)[0]))??/*?數(shù)組元素個(gè)數(shù)?*/ typedef?struct{ ????INT32U??dwJitterBuffer; ????INT32U??dwFramePerPkt; ????INT32U??dwQueueSize; }QUEUE_SIZE_MAP; /*?gCesQueueSizeMap也可以(JitterBuffer?/?FramePerPkt)值為索引,更加緊湊?*/ static?QUEUE_SIZE_MAP?gCesQueueSizeMap[]=?{ ???????{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?*?N?Packet?=?2?^?QueueSize *????????????JitterBuffer為125us幀速率的倍數(shù), *????????????FramePerPkt為每個(gè)分組的幀數(shù), *??????????? QueueSize向上取整,最大為7。 **********************************************************/ INT32U?CalcQueueSize(INT32U?dwJitterBuffer,?INT32U?dwFramePerPkt) { ????INT8U?ucIdx?=?0,?ucNum?=?0; ????//本函數(shù)暫時(shí)僅考慮E1 ????ucNum?=?ARRAY_NUM(gCesQueueSizeMap); ????for(ucIdx?=?0;?ucIdx?版本控制
問題:控制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; } else?if?(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的處理代碼 else?if?(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語言中更簡(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新增列(可控版本) **********************************************************************/ #define?CTRL_VERS_NUM????3 #define?CTRL_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; 消息處理
問題:終端輸入不同的打印命令,調(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)方法如下:
void?logall(void) { ????g_log_control[0]?=?0xFFFFFFFF; } void?noanylog(void) { ????g_log_control[0]?=?0; } void?logOam(void) { ????g_log_control[0]?|=?(0x01?<以下示出C語言中更簡(jiǎn)潔的實(shí)現(xiàn)方式:
typedef?struct{ ????OAM_LOG_OFF?=?(INT8U)0, ????OAM_LOG_ON??=?(INT8U)1 }E_OAM_LOG_MODE; typedef?FUNC_STATUS?(*OamLogHandler)(VOID); typedef?struct{ ????CHAR???????????*pszLogCls;????/*?打印級(jí)別?*/ ????E_OAM_LOG_MODE?eLogMode;??????/*?打印模式?*/ ????OamLogHandler??fnLogHandler;??/*?打印函數(shù)?*/ }T_OAM_LOG_MAP; T_OAM_LOG_MAP?gOamLogMap[]?=?{ ????{"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} }; INT32U?gOamLogMapNum?=?sizeof(gOamLogMap)?/?sizeof(T_OAM_LOG_MAP); VOID?logExec(CHAR?*pszName,?INT8U?ucSwitch) { ????INT8U?ucIdx?=?0; ????for(;?ucIdx? 這種表驅(qū)動(dòng)消息處理實(shí)現(xiàn)的優(yōu)點(diǎn)如下:
1.增強(qiáng)可讀性,消息如何處理從表中一目了然。2.增強(qiáng)可擴(kuò)展性。更容易修改,要增加新的消息,只要修改數(shù)據(jù)即可,不需要修改流程。
3.降低復(fù)雜度。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達(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):
/*?日志控制類型定義?*/ typedef?enum { ????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,語音日志?*/ ????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種日志類型。 ?*?訪問說明:通過GetOmciLogCtrl/SetOmciLogCtrl/OmciLogCtrl函數(shù)訪問/設(shè)置控制字。 ?*****************************************************************************/ static?INT32U?gOmciLogCtrl?=?0; //日志類型字符串?dāng)?shù)組,下標(biāo)為各字符串所對(duì)應(yīng)的日志類型枚舉值。 static?const?INT8U*?paLogTypeName[]?=?{ ????"Norm",????????"Frame",???"Pon",??"Ethernet",??"Internet", ????"Multicast",???"Qos",?????"Ces",??"Voip",??????"Alarm", ????"Performance",?"Version",?"Xdsl",??"Db" }; static?const?INT8U??ucLogTypeNameNum?=?sizeof(paLogTypeName)?/?sizeof(paLogTypeName[0]); static?VOID?SetGlobalLogCtrl(E_LOG_TYPE?eLogType,?INT8U?ucLogSwitch) { ????if(LOG_ON?==?ucLogSwitch) ????????gOmciLogCtrl?=?LOG_ALL; ????else ????????gOmciLogCtrl?=?0; } static?VOID?SetSpecificLogCtrl(E_LOG_TYPE?eLogType,?INT8U?ucLogSwitch) { ????if(LOG_ON?==?ucLogSwitch) ????????SET_BIT(gOmciLogCtrl,?eLogType); ????else ????????CLR_BIT(gOmciLogCtrl,?eLogType); } VOID?OmciLogCtrl(CHAR?*pszLogType,?INT8U?ucLogSwitch) { ????if(0?==?strncasecmp(pszLogType,?"All",?LOG_TYPE_CMP_LEN)) ????{ ????????SetGlobalLogCtrl(LOG_ALL,?ucLogSwitch); ????????return; ????} ????INT8U?ucIdx?=?0; ????for(ucIdx?=?0;?ucIdx?編程思想
表驅(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)單的程序邏輯讓人類來驗(yàn)證也很困難,但就算是很復(fù)雜的數(shù)據(jù),對(duì)人類來說,還是相對(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ù)“封裝”。
附錄
網(wǎng)友觀點(diǎn)
(以下觀點(diǎn)摘自博客園網(wǎng)友“七心葵”的回帖,非常具有啟發(fā)性。)
Booch的《面向?qū)ο蠓治雠c設(shè)計(jì)》一書中,提到所有的程序設(shè)計(jì)語言大概有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ā)過程中很常用,但是少有人專門提到這個(gè)詞。
數(shù)據(jù)驅(qū)動(dòng)編程有很多名字:元編程,解釋器/虛擬機(jī),LOP/微語言/DSL等。包括聲明式編程、標(biāo)記語言、甚至所見即所得的拖放控件,都算是數(shù)據(jù)驅(qū)動(dòng)編程的一種吧。
數(shù)據(jù)驅(qū)動(dòng)編程可以幫助處理復(fù)雜性,和結(jié)構(gòu)化編程、OO 均可相容。(正交的角度)
將變和不變的部分分離,策略和機(jī)制分離,由此聯(lián)想到的還有:(數(shù)據(jù)和代碼的分離,微語言和解釋器的分離,被生成代碼和代碼生成器的分離);更近一步:(微內(nèi)核插件式體系結(jié)構(gòu))
元編程應(yīng)該說是更加泛化的數(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)元編程一般通過解釋器(或虛擬機(jī))加以實(shí)現(xiàn)。
數(shù)據(jù)驅(qū)動(dòng)編程當(dāng)然也不應(yīng)該說是“反抽象的”,但的確與“OO抽象”的思維方式是迥然不同,涇渭分明的,如TAOUP一書中所述:“在Unix的模塊化傳統(tǒng)和圍繞OO語言發(fā)展起來的使用模式之間,存在著緊張的對(duì)立關(guān)系”應(yīng)該說數(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、人思維的直觀性(人的視覺能力和模糊思維能力),這意味這兩點(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ù)量的比例是一定的,所以語言有具有表現(xiàn)力,要體現(xiàn)表達(dá)的經(jīng)濟(jì)性)
所以要機(jī)制與策略分離,要數(shù)據(jù)和代碼分離(數(shù)據(jù)驅(qū)動(dòng)編程),要微語言,要DSL,要LOP……
e、 人是有創(chuàng)造欲,有現(xiàn)實(shí)利益心的(只要偶可能總是不夠遵從規(guī)范,或想創(chuàng)造規(guī)范謀利——只要成本能承受,在硬件領(lǐng)域就不行)
另外,開一個(gè)有意思的玩笑,Unix編程藝術(shù)藝術(shù)的英文縮寫為TAOUP,我覺得可以理解為UP之TAO——向上拋出之道——將復(fù)雜的易變的邏輯作為數(shù)據(jù)或更高層代碼拋給上層!
函數(shù)指針
“消息處理”一節(jié)示例中的函數(shù)指針有點(diǎn)插件結(jié)構(gòu)的味道。可對(duì)這些插件進(jìn)行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對(duì)事件處理函數(shù)的查找又是隔離的(隔離變化)。
函數(shù)指針非常有用,但使用時(shí)需注意其缺陷:無法檢查參數(shù)(parameter)和返回值(return value)的類型。因?yàn)楹瘮?shù)已經(jīng)退化成指針,而指針不攜帶這些類型信息。缺少類型檢查,當(dāng)參數(shù)或返回值不一致時(shí),可能會(huì)造成嚴(yán)重的錯(cuò)誤。
例如,定義三個(gè)函數(shù),分別具有兩個(gè)參數(shù):
int?max(int?x,?int?y)??{??return?x>y?x:y;??} int?min(int?x,?int?y)??{??return?x
而處理函數(shù)卻定義為:
int?process(int?x,?int?y,?int?(*f)())??{??return?(*f)(x,?y);??}其中,第三個(gè)參數(shù)是一個(gè)沒有參數(shù)且返回int型變量的函數(shù)指針。但后面卻用process(a,b,max)的方式進(jìn)行調(diào)用,max帶有兩個(gè)參數(shù)。若編譯器未檢查出錯(cuò)誤,而又不小心將return (*f)(x,y);寫成return (*f)(x);,那么后果可能很嚴(yán)重。因此在C語言中使用函數(shù)指針時(shí),一定要小心"類型陷阱"。
編輯:黃飛
?
評(píng)論
查看更多