“數(shù)據(jù)壓倒一切。如果選擇了正確的數(shù)據(jù)結(jié)構(gòu)并把一切組織的井井有條,正確的算法就不言自明。編程的核心是數(shù)據(jù)結(jié)構(gòu),而不是算法?!猂ob Pike”
說明
本文基于這樣的認識:數(shù)據(jù)是易變的,邏輯是穩(wěn)定的。
本文例舉的編程實現(xiàn)多為代碼片段,但不影響描述的完整性。
本文例舉的編程雖然基于C語言,但其編程思想也適用于其他語言。
此外,本文不涉及語言相關(guān)的運行效率討論。
概念提出
所謂表驅(qū)動法(Table-Driven Approach)簡而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫的一種體現(xiàn)。
根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動法,即以每個字的字形為依據(jù),計算出一個索引值,并映射到對應的頁數(shù)。相比一頁一頁地順序翻字典查字,部首檢字法效率極高。
具體到編程方面,在數(shù)據(jù)不多時可用邏輯判斷語句(if…else或switch…case)來獲取值;但隨著數(shù)據(jù)的增多,邏輯語句會越來越長,此時表驅(qū)動法的優(yōu)勢就開始顯現(xiàn)。
例如,用36進制(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';
}
?
當然也可以用switch…case結(jié)構(gòu),但實現(xiàn)都很冗長。而用表驅(qū)動法(將numChar存入數(shù)組)則非常直觀和簡潔。如:
?
CHAR?aNumChars[]?=?{'0',?'1',?'2',?/*3~9*/'A',?'B',?'C',?/*D~Y*/'Z'};
CHAR?ucNumChar?=?aNumChars[ucNum?%?sizeof(aNumChars)];
?
像這樣直接將變量當作下數(shù)組下標來讀取數(shù)值的方法就是直接查表法。
注意,如果熟悉字符串操作,則上述寫法可以更簡潔:
?
CHAR?ucNumChar?=?"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[ucNum];
?
使用表驅(qū)動法時需要關(guān)注兩個問題:一是如何查表,從表中讀取正確的數(shù)據(jù);二是表里存放什么,如數(shù)值或函數(shù)指針。前者參見1.1節(jié)“查表方式”內(nèi)容,后者參見1.2節(jié)“實戰(zhàn)示例”內(nèi)容。
查表方式
常用的查表方式有直接查找、索引查找和分段查找等。
直接查找
即直接通過數(shù)組下標獲取到數(shù)據(jù)。如果熟悉哈希表的話,可以很容易看出這種查表方式就是哈希表的直接訪問法。
如獲取星期名稱,邏輯判斷語句如下:
?
if(0?==?ucDay)
{
????pszDayName?=?"Sunday";
}
else?if(1?==?ucDay)
{
????pszDayName?=?"Monday";
}
//...?...
else?if(6?==?ucDay)
{
????pszDayName?=?"Saturday";
}
?
而實現(xiàn)同樣的功能,可將這些數(shù)據(jù)存儲到一個表里:
?
CHAR?*paNumChars[]?=?{"Sunday",?"Monday",?"Tuesday",?"Wednesday",?"Thursday",?"Friday",??"Saturday"};
CHAR?*pszDayName?=?paNumChars[ucDay];
?
類似哈希表特性,表驅(qū)動法適用于無需有序遍歷數(shù)據(jù),且數(shù)據(jù)量大小可提前預測的情況。
對于過于復雜和龐大的判斷,可將數(shù)據(jù)存為文件,需要時加載文件初始化數(shù)組,從而在不修改程序的情況下調(diào)整里面的數(shù)值。
有時,訪問之前需要先進行一次鍵值轉(zhuǎn)換。如表驅(qū)動法表示端口忙閑時,需將槽位端口號映射為全局編號。所生成的端口數(shù)目大小的數(shù)組,其下標對應全局端口編號,元素值表示相應端口的忙閑狀態(tài)。
索引查找
有時通過一次鍵值轉(zhuǎn)換,依然無法把數(shù)據(jù)(如英文單詞等)轉(zhuǎn)為鍵值。此時可將轉(zhuǎn)換的對應關(guān)系寫到一個索引表里,即索引訪問。
如現(xiàn)有100件商品,4位編號,范圍從0000到9999。此時只需要申請一個長度為100的數(shù)組,且對應2位鍵值。但將4位的編號轉(zhuǎn)換為2位的鍵值,可能過于復雜或沒有規(guī)律,最合適的方法是建立一個保存該轉(zhuǎn)換關(guān)系的索引表。采用索引訪問既節(jié)省內(nèi)存,又方便維護。比如索引A表示通過名稱訪問,索引B表示通過編號訪問。
分段查找
通過確定數(shù)據(jù)所處的范圍確定分類(下標)。有的數(shù)據(jù)可分成若干區(qū)間,即具有階梯性,如分數(shù)等級。此時可將每個區(qū)間的上限(或下限)存到一個表中,將對應的值存到另一表中,通過第一個表確定所處的區(qū)段,再由區(qū)段下標在第二個表里讀取相應數(shù)值。注意要留意端點,可用二分法查找,另外可考慮通過索引方法來代替。
如根據(jù)分數(shù)查績效等級:
?
#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ù)庫的雛形,并可擴展支持更為復雜的數(shù)據(jù)。其查表方式通常為索引查找,偶爾也為分段查找;當索引具有規(guī)律性(如連續(xù)整數(shù))時,退化為直接查找。
使用分段查找法時應注意邊界,將每一分段范圍的上界值都考慮在內(nèi)。找出所有不在最高一級范圍內(nèi)的值,然后把剩下的值全部歸入最高一級中。有時需要人為地為最高一級范圍添加一個上界。
同時應小心不要錯誤地用“<”來代替“<=”。要保證循環(huán)在找出屬于最高一級范圍內(nèi)的值后恰當?shù)亟Y(jié)束,同時也要保證恰當處理范圍邊界。
實戰(zhàn)示例
本節(jié)多數(shù)示例取自實際項目。表形式為一維數(shù)組、二維數(shù)組和結(jié)構(gòu)體數(shù)組;表內(nèi)容有數(shù)據(jù)、字符串和函數(shù)指針。基于表驅(qū)動的思想,表形式和表內(nèi)容可衍生出豐富的組合。
字符統(tǒng)計
問題:統(tǒng)計用戶輸入的一串數(shù)字中每個數(shù)字出現(xiàn)的次數(shù)。
普通解法主體代碼如下:
?
INT32U?aDigitCharNum[10]?=?{0};?/*?輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù)?*/
INT32U?dwStrLen?=?strlen(szDigits);
INT32U?dwStrIdx?=?0;
for(;?dwStrIdx?
?
這種解法的缺點顯而易見,既不美觀也不靈活。其問題關(guān)鍵在于未將數(shù)字字符與數(shù)組aDigitCharNum下標直接關(guān)聯(lián)起來。
以下示出更簡潔的實現(xiàn)方式:
?
for(;?dwStrIdx?
?
上述實現(xiàn)考慮到0也為數(shù)字字符。該解法也可擴展至統(tǒng)計所有ASCII可見字符。
月天校驗
問題:對給定年份和月份的天數(shù)進行校驗(需區(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;
}
?
以下示出更簡潔的實現(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ù),下標對應月份?*/
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è)務類型(Bitmap)構(gòu)造業(yè)務類型名稱字符串。
普通解法主體代碼如下:
?
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語言中更簡潔的實現(xiàn)方式:
?
/*?獲取var變量第bit位,編號從右至左?*/
#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?
?
新的實現(xiàn)將數(shù)據(jù)和邏輯分離,維護起來非常方便。只要邏輯(規(guī)則)不變,則唯一可能的改動就是數(shù)據(jù)(paSvrNames)。
值名解析
問題:根據(jù)枚舉變量取值輸出其對應的字符串,如PORT_FE(1)輸出“Fe”。
?
//值名映射表結(jié)構(gòu)體定義,用于數(shù)值解析器
typedef?struct{
????INT32U?dwElem;????//待解析數(shù)值,通常為枚舉變量
????CHAR*??pszName;???//指向數(shù)值所對應解析名字符串的指針
}T_NAME_PARSER;
/******************************************************************************
*?函數(shù)名稱:??NameParser
*?功能說明:??數(shù)值解析器,將給定數(shù)值轉(zhuǎn)換為對應的具名字符串
*?輸入參數(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ù)值所對應的具名字符串
?????????????當無法解析給定數(shù)值時,若pszDefName為空,則返回數(shù)值對應的16進制格式
?????????????字符串;否則返回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標準禁止對void指針進行算法操作;GNU標準則指定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的簡單應用示例:
?
//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在實際項目中有十余個條目,若采用邏輯鏈實現(xiàn)將非常冗長。
取值映射
問題:不同模塊間同一參數(shù)枚舉值取值可能有所差異,需要適配。
此處不再給出普通的switch…case或if…else if…else結(jié)構(gòu),而直接示出以下表驅(qū)動實現(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?
?
相應地,從loopMIBState映射到loopMEState需要定義一個ConvertLoopMIBStateToMEState函數(shù)。更進一步,所有類似的一對一映射關(guān)系都必須如上的映射(轉(zhuǎn)換)函數(shù),相當繁瑣。
事實上,從抽象層面看,該映射關(guān)系非常簡單。提取共性后定義帶參數(shù)宏,如下所示:
?
/**********************************************************
*?功能描述:進行二維數(shù)組映射表的一對一映射,用于參數(shù)適配
*?參數(shù)說明:map ???????--?二維數(shù)組映射表
????????????elemSrc????--?映射源,即待映射的元素值
????????????elemDest???--?映射源對應的映射結(jié)果
??????????? direction ?--?映射方向字節(jié),表示從數(shù)組哪列映射至哪列。
??????????????????????????高4位對應映射源列,低4位對應映射結(jié)果列。
????????????defaultVal?--?映射失敗時置映射結(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)換時直接調(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???/*?默認jitterbuf為2000us,而1幀=125us?*/
#define??CES_JITTERBUF_STEP???????????(INT32U)125????/*?jitterbuf步長為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ù)組元素個數(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計算QueueSize
*?注意事項:配置的最大緩存深度
*????????????=?2?*?JitterBuffer?/?FramePerPkt
*????????????=?2?*?N?Packet?=?2?^?QueueSize
*????????????JitterBuffer為125us幀速率的倍數(shù),
*????????????FramePerPkt為每個分組的幀數(shù),
*??????????? QueueSize向上取整,最大為7。
**********************************************************/
INT32U?CalcQueueSize(INT32U?dwJitterBuffer,?INT32U?dwFramePerPkt)
{
????INT8U?ucIdx?=?0,?ucNum?=?0;
????//本函數(shù)暫時僅考慮E1
????ucNum?=?ARRAY_NUM(gCesQueueSizeMap);
????for(ucIdx?=?0;?ucIdx?
?
版本控制
問題:控制OLT與ONU之間的版本協(xié)商。ONU本地設(shè)置三比特控制字,其中bit2(MSB)~bit0(LSB)分別對應0x21、0x30和0xAA版本號;且bitX為0表示上報對應版本號,bitX為1表示不上報對應版本號。其他版本號如0x20、0x13和0x1必須上報,即不受控制。
最初的實現(xiàn)采用if…else if…else結(jié)構(gòu),代碼非常冗長,如下:
?
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語言中更簡潔的實現(xiàn)方式(基于二維數(shù)組):
?
/**********************************************************************
*?版本控制字數(shù)組定義
* gOamCtrlCode:?? Bitmap控制字。Bit-X為0時上報對應版本,Bit-X為1時屏蔽對應版本。
* CTRL_VERS_NUM:??可控版本個數(shù)。
* CTRL_CODE_NUM:??控制字個數(shù)。與CTRL_VERS_NUM有關(guān)。
* gOamVerCtrlMap:?版本控制字數(shù)組。行對應控制字,列對應可控版本。
??????????????????元素值為0時不上報對應版本,元素值非0時上報該元素值。
* Note:?該數(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)用相應的打印函數(shù),以控制不同級別的打印。
這是一段消息(事件)驅(qū)動程序。本模塊接收其他模塊(如串口驅(qū)動)發(fā)送的消息,根據(jù)消息中的打印級別字符串和開關(guān)模式,調(diào)用不同函數(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語言中更簡潔的實現(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;????/*?打印級別?*/
????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ū)動消息處理實現(xiàn)的優(yōu)點如下:
1.增強可讀性,消息如何處理從表中一目了然。
2.增強可擴展性。更容易修改,要增加新的消息,只要修改數(shù)據(jù)即可,不需要修改流程。
3.降低復雜度。通過把程序邏輯的復雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達到控制復雜度的目標。
4.主干清晰,代碼重用。
若各索引為順序枚舉值,則建立多維數(shù)組(每維對應一個索引),根據(jù)下標直接定位到處理函數(shù),效率會更高。
注意,考慮到本節(jié)實例中l(wèi)ogOam/logPon或nologOam/nologPon等函數(shù)本質(zhì)上是基于打印級別的比特操作,因此可進一步簡化。以下例舉其相似實現(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)絡日志?*/
????LOG_MULT,????????????/*?Multicast,組播日志?*/
????LOG_QOS,?????????????/*?QOS,流量日志?*/
????LOG_CES,?????????????/*?Ces,TDM電路仿真日志?*/
????LOG_VOIP,????????????/*?Voip,語音日志?*/
????LOG_ALM,?????????????/*?Alarm,告警日志?*/
????LOG_PERF,????????????/*?Performance,性能統(tǒng)計日志?*/
????LOG_VER,?????????????/*?Version,軟件升級日志?*/
????LOG_XDSL,????????????/*?xDsl日志?*/
????LOG_DB,??????????????/*?數(shù)據(jù)庫操作日志?*/
????//新日志類型在此處擴展,共支持32種日志類型
????LOG_ALL?=?UINT_MAX???/*?所有日志類型?*/
}E_LOG_TYPE;
/*****************************************************************************
?*?變量名稱:gOmciLogCtrl
?*?作用描述:OMCI日志控制字,BitMap格式(比特編號從LSB至MSB依次為Bit0->BitN)。
?*?????????? Bit0~N分別對應E_LOG_TYPE各枚舉值(除LOG_ALL外)。
?*?????????? BitX為0時關(guān)閉日志類型對應的日志功能,BitX為1時則予以打開。
?*?變量范圍:該變量為四字節(jié)整型靜態(tài)全局變量,即支持32種日志類型。
?*?訪問說明:通過GetOmciLogCtrl/SetOmciLogCtrl/OmciLogCtrl函數(shù)訪問/設(shè)置控制字。
?*****************************************************************************/
static?INT32U?gOmciLogCtrl?=?0;
//日志類型字符串數(shù)組,下標為各字符串所對應的日志類型枚舉值。
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ū)動法屬于數(shù)據(jù)驅(qū)動編程的一種,其核心思想在《Unix編程藝術(shù)》和《代碼大全2》中均有闡述。兩者均認為人類閱讀復雜數(shù)據(jù)結(jié)構(gòu)遠比復雜的控制流程容易,即相對于程序邏輯,人類更擅長于處理數(shù)據(jù)。
本節(jié)將由Unix設(shè)計原則中的“分離原則”和“表示原則”展開。
分離原則:策略同機制分離,接口同引擎分離
機制即提供的功能;策略即如何使用功能。
策略的變化要遠遠快于機制的變化。將兩者分離,可以使機制相對保持穩(wěn)定,而同時支持策略的變化。
代碼大全中提到“隔離變化”的概念,以及設(shè)計模式中提到的將易變化的部分和不易變化的部分分離也是這個思路。
表示原則:把知識疊入數(shù)據(jù)以求邏輯質(zhì)樸而健壯
即使最簡單的程序邏輯讓人類來驗證也很困難,但就算是很復雜的數(shù)據(jù),對人類來說,還是相對容易推導和建模的。數(shù)據(jù)比編程邏輯更容易駕馭。在復雜數(shù)據(jù)和復雜代碼中選擇,寧可選擇前者。更進一步,在設(shè)計中,應該主動將代碼的復雜度轉(zhuǎn)移到數(shù)據(jù)中去(參考“版本控制”)。
在“消息處理”示例中,每個消息處理的邏輯不變,但消息可能是變化的。將容易變化的消息和不容易變化的查找邏輯分離,即“隔離變化”。此外,該例也體現(xiàn)消息內(nèi)部的處理邏輯(機制)和不同的消息處理(策略)分離。
數(shù)據(jù)驅(qū)動編程可以應用于:
1.函數(shù)級設(shè)計,如本文示例。2.程序級設(shè)計,如用表驅(qū)動法實現(xiàn)狀態(tài)機。3.系統(tǒng)級設(shè)計,如DSL。
注意,數(shù)據(jù)驅(qū)動編程不是全新的編程模型,只是一種設(shè)計思路,在Unix/Linux開源社區(qū)應用很多。數(shù)據(jù)驅(qū)動編程中,數(shù)據(jù)不但表示某個對象的狀態(tài),實際上還定義程序的流程,這點不同于面向?qū)ο笤O(shè)計中的數(shù)據(jù)“封裝”。
附錄
網(wǎng)友觀點
(以下觀點摘自博客園網(wǎng)友“七心葵”的回帖,非常具有啟發(fā)性。)
Booch的《面向?qū)ο蠓治雠c設(shè)計》一書中,提到所有的程序設(shè)計語言大概有3個源流:結(jié)構(gòu)化編程;面向?qū)ο缶幊?;?shù)據(jù)驅(qū)動編程。
我認為數(shù)據(jù)驅(qū)動編程的本質(zhì)是“參數(shù)化抽象”的思想,不同于OO的“規(guī)范化抽象”的思想。
數(shù)據(jù)驅(qū)動編程在網(wǎng)絡游戲開發(fā)過程中很常用,但是少有人專門提到這個詞。
數(shù)據(jù)驅(qū)動編程有很多名字:元編程,解釋器/虛擬機,LOP/微語言/DSL等。包括聲明式編程、標記語言、甚至所見即所得的拖放控件,都算是數(shù)據(jù)驅(qū)動編程的一種吧。
數(shù)據(jù)驅(qū)動編程可以幫助處理復雜性,和結(jié)構(gòu)化編程、OO 均可相容。(正交的角度)
將變和不變的部分分離,策略和機制分離,由此聯(lián)想到的還有:(數(shù)據(jù)和代碼的分離,微語言和解釋器的分離,被生成代碼和代碼生成器的分離);更近一步:(微內(nèi)核插件式體系結(jié)構(gòu))
元編程應該說是更加泛化的數(shù)據(jù)驅(qū)動編程,元編程不是新加入一個間接層,而是退居一步,使得當前的層變成一個間接層。元編程分為靜態(tài)元編程(編譯時)和動態(tài)元編程(運行時),靜態(tài)元編程本質(zhì)上是一種 代碼生成技術(shù)或者編譯器技術(shù);動態(tài)元編程一般通過解釋器(或虛擬機)加以實現(xiàn)。
數(shù)據(jù)驅(qū)動編程當然也不應該說是“反抽象的”,但的確與“OO抽象”的思維方式是迥然不同,涇渭分明的,如TAOUP一書中所述:“在Unix的模塊化傳統(tǒng)和圍繞OO語言發(fā)展起來的使用模式之間,存在著緊張的對立關(guān)系”應該說數(shù)據(jù)驅(qū)動編程的思路與結(jié)構(gòu)化編程和OO是正交的,更類似一種“跳出三界外,不在五行中”的做法。
編程和人的關(guān)系
人類心智的限制,一切的背后都有人的因素作為依據(jù):
a 人同時關(guān)注的信息數(shù)量:7+-2 (所以要分模塊)
b 人接收一組新信息的平均時間5s (所以要簡單,系統(tǒng)總的模塊數(shù)不要太多)
c 人思維的直觀性(人的視覺能力和模糊思維能力),這意味這兩點:
A “直”——更善于思考自己能直接接觸把玩的東西;(所以要“淺平透”、使用具象的設(shè)計,要盡量代碼中只有順直的流程),
B “觀”——更善于觀圖而不是推算邏輯;(所以要表驅(qū)動法,數(shù)據(jù)驅(qū)動編程,要UML,要可視化編程——當然MDA是太理想化了)
d 人不能持續(xù)集中注意力(人在一定的代碼行數(shù)中產(chǎn)生的bug數(shù)量的比例是一定的,所以語言有具有表現(xiàn)力,要體現(xiàn)表達的經(jīng)濟性)
所以要機制與策略分離,要數(shù)據(jù)和代碼分離(數(shù)據(jù)驅(qū)動編程),要微語言,要DSL,要LOP……
e 人是有創(chuàng)造欲,有現(xiàn)實利益心的(只要偶可能總是不夠遵從規(guī)范,或想創(chuàng)造規(guī)范謀利——只要成本能承受,在硬件領(lǐng)域就不行)
另外,開一個有意思的玩笑,Unix編程藝術(shù)藝術(shù)的英文縮寫為TAOUP,我覺得可以理解為UP之TAO——向上拋出之道——將復雜的易變的邏輯作為數(shù)據(jù)或更高層代碼拋給上層!
函數(shù)指針
“消息處理”一節(jié)示例中的函數(shù)指針有點插件結(jié)構(gòu)的味道??蓪@些插件進行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對事件處理函數(shù)的查找又是隔離的(隔離變化)。
函數(shù)指針非常有用,但使用時需注意其缺陷:無法檢查參數(shù)(parameter)和返回值(return value)的類型。因為函數(shù)已經(jīng)退化成指針,而指針不攜帶這些類型信息。缺少類型檢查,當參數(shù)或返回值不一致時,可能會造成嚴重的錯誤。
例如,定義三個函數(shù),分別具有兩個參數(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);??}
?
其中,第三個參數(shù)是一個沒有參數(shù)且返回int型變量的函數(shù)指針。但后面卻用process(a,b,max)的方式進行調(diào)用,max帶有兩個參數(shù)。若編譯器未檢查出錯誤,而又不小心將return (*f)(x,y);寫成return (*f)(x);,那么后果可能很嚴重。
因此在C語言中使用函數(shù)指針時,一定要小心"類型陷阱"。
審核編輯:湯梓紅
電子發(fā)燒友App










評論