C语言精华知识之表驱动法编程实践Word文档下载推荐.docx
- 文档编号:21047129
- 上传时间:2023-01-27
- 格式:DOCX
- 页数:21
- 大小:28.57KB
C语言精华知识之表驱动法编程实践Word文档下载推荐.docx
《C语言精华知识之表驱动法编程实践Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《C语言精华知识之表驱动法编程实践Word文档下载推荐.docx(21页珍藏版)》请在冰豆网上搜索。
CHARucNumChar=aNumChars[ucNum%sizeof(aNumChars)];
像这样直接将变量当作下数组下标来读取数值的方法就是直接查表法。
注意,如果熟悉字符串操作,则上述写法可以更简洁:
CHARucNumChar="
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
[ucNum];
使用表驱动法时需要关注两个问题:
一是如何查表,从表中读取正确的数据;
二是表里存放什么,如数值或函数指针。
1.1查表方式
常用的查表方式有直接查找、索引查找和分段查找等。
1.1.1直接查找
即直接通过数组下标获取到数据。
如果熟悉哈希表的话,可以很容易看出这种查表方式就是哈希表的直接访问法。
如获取星期名称,逻辑判断语句如下:
if(0==ucDay)
pszDayName="
Sunday"
elseif(1==ucDay)
Monday"
elseif(6==ucDay)
Saturday"
而实现同样的功能,可将这些数据存储到一个表里:
CHAR*paNumChars[]={"
"
Tuesday"
Wednesday"
Thursday"
Friday"
CHAR*pszDayName=paNumChars[ucDay];
类似哈希表特性,表驱动法适用于无需有序遍历数据,且数据量大小可提前预测的情况。
对于过于复杂和庞大的判断,可将数据存为文件,需要时加载文件初始化数组,从而在不修改程序的情况下调整里面的数值。
有时,访问之前需要先进行一次键值转换。
如表驱动法表示端口忙闲时,需将槽位端口号映射为全局编号。
所生成的端口数目大小的数组,其下标对应全局端口编号,元素值表示相应端口的忙闲状态。
1.1.2索引查找
有时通过一次键值转换,依然无法把数据(如英文单词等)转为键值。
此时可将转换的对应关系写到一个索引表里,即索引访问。
如现有100件商品,4位编号,范围从0000到9999。
此时只需要申请一个长度为100的数组,且对应2位键值。
但将4位的编号转换为2位的键值,可能过于复杂或没有规律,最合适的方法是建立一个保存该转换关系的索引表。
采用索引访问既节省内存,又方便维护。
比如索引A表示通过名称访问,索引B表示通过编号访问。
1.1.3分段查找
通过确定数据所处的范围确定分类(下标)。
有的数据可分成若干区间,即具有阶梯性,如分数等级。
此时可将每个区间的上限(或下限)存到一个表中,将对应的值存到另一表中,通过第一个表确定所处的区段,再由区段下标在第二个表里读取相应数值。
注意要留意端点,可用二分法查找,另外可考虑通过索引方法来代替。
如根据分数查绩效等级:
#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<
MAX_GRADE_LEVEL;
ucLevel++)
{
if(dScore<
aRangeLimit[ucLevel])
returnpaGrades[ucLevel];
}
returnpaGrades[0];
上述两张表(数组)也可合并为一张表(结构体数组),如下所示:
typedefstruct{
DOUBLEaRangeLimit;
CHAR*pszGrade;
}T_GRADE_MAP;
T_GRADE_MAPgGradeMap[MAX_GRADE_LEVEL]={
{50.0,"
},
{60.0,"
{70.0,"
{80.0,"
{100.0,"
gGradeMap[ucLevel].aRangeLimit)
returngGradeMap[ucLevel].pszGrade;
returngGradeMap[0].pszGrade;
该表结构已具备的数据库的雏形,并可扩展支持更为复杂的数据。
其查表方式通常为索引查找,偶尔也为分段查找;
当索引具有规律性(如连续整数)时,退化为直接查找。
使用分段查找法时应注意边界,将每一分段范围的上界值都考虑在内。
找出所有不在最高一级范围内的值,然后把剩下的值全部归入最高一级中。
有时需要人为地为最高一级范围添加一个上界。
同时应小心不要错误地用“<
”来代替“<
=”。
要保证循环在找出属于最高一级范围内的值后恰当地结束,同时也要保证恰当处理范围边界。
1.2实战示例
本节多数示例取自实际项目。
表形式为一维数组、二维数组和结构体数组;
表内容有数据、字符串和函数指针。
基于表驱动的思想,表形式和表内容可衍生出丰富的组合。
1.2.1字符统计
问题:
统计用户输入的一串数字中每个数字出现的次数。
普通解法主体代码如下:
INT32UaDigitCharNum[10]={0};
/*输入字符串中各数字字符出现的次数*/
INT32UdwStrLen=strlen(szDigits);
INT32UdwStrIdx=0;
for(;
dwStrIdx<
dwStrLen;
dwStrIdx++)
switch(szDigits[dwStrIdx])
case'
:
aDigitCharNum[0]++;
break;
aDigitCharNum[1]++;
//......
9'
aDigitCharNum[8]++;
这种解法的缺点显而易见,既不美观也不灵活。
其问题关键在于未将数字字符与数组aDigitCharNum下标直接关联起来。
以下示出更简洁的实现方式:
aDigitCharNum[szDigits[dwStrIdx]-'
]++;
上述实现考虑到0也为数字字符。
该解法也可扩展至统计所有ASCII可见字符。
1.2.2月天校验
对给定年份和月份的天数进行校验(需区分平年和闰年)。
switch(OnuTime.Month)
case1:
case3:
case5:
case7:
case8:
case10:
case12:
if(OnuTime.Day>
31||OnuTime.Day<
1)
CtcOamLog(FUNCTION_Pon,"
Don'
tsupportthisDay:
%d(1~31)!
!
\n"
OnuTime.Day);
retcode=S_ERROR;
case2:
if(((OnuTime.Year%4==0)&
&
(OnuTime.Year%100!
=0))||(OnuTime.Year%400==0))
29||OnuTime.Day<
%d(1~29)!
else
28||OnuTime.Day<
%d(1~28)!
case4:
case6:
case9:
case11:
30||OnuTime.Day<
%d(1~30)!
default:
tsupportthisMonth:
%d(1~12)!
OnuTime.Month);
#defineMONTH_OF_YEAR12/*一年中的月份数*/
/*闰年:
能被4整除且不能被100整除,或能被400整除*/
#defineIS_LEAP_YEAR(year)((((year)%4==0)&
((year)%100!
=0))||((year)%400==0))
/*平年中的各月天数,下标对应月份*/
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.Day<
1)||(OnuTime.Day>
ucMaxDay)
Month%ddoesn'
thavethisDay:
%d(1~%d)!
OnuTime.Month,OnuTime.Day,ucMaxDay);
1.2.3名称构造
根据WAN接口承载的业务类型(Bitmap)构造业务类型名称字符串。
voidSub_SetServerType(INT8U*ServerType,INT16Uwan_servertype)
if((wan_servertype&
0x0001)==0x0001)
strcat(ServerType,"
_INTERNET"
);
0x0002)==0x0002)
_TR069"
0x0004)==0x0004)
_VOIP"
0x0008)==0x0008)
_OTHER"
以下示出C语言中更简洁的实现方式:
/*获取var变量第bit位,编号从右至左*/
#defineGET_BIT(var,bit)(((var)>
>
(bit))&
0x1)
constCHAR*paSvrNames[]={"
constINT8UucSvrNameNum=sizeof(paSvrNames)/sizeof(paSvrNames[0]);
VOIDSetServerType(CHAR*pszSvrType,INT16UwSvrType)
INT8UucIdx=0;
ucIdx<
ucSvrNameNum;
ucIdx++)
if(1==GET_BIT(wSvrType,ucIdx))
strcat(pszSvrType,paSvrNames[ucIdx]);
新的实现将数据和逻辑分离,维护起来非常方便。
只要逻辑(规则)不变,则唯一可能的改动就是数据(paSvrNames)。
1.2.4值名解析
根据枚举变量取值输出其对应的字符串,如PORT_FE
(1)输出“Fe”。
//值名映射表结构体定义,用于数值解析器
INT32UdwElem;
//待解析数值,通常为枚举变量
CHAR*pszName;
//指向数值所对应解析名字符串的指针
}T_NAME_PARSER;
/******************************************************************************
*函数名称:
NameParser
*功能说明:
数值解析器,将给定数值转换为对应的具名字符串
*输入参数:
VOID*pvMap:
值名映射表数组,含T_NAME_PARSER结构体类型元素VOID指针允许用户在保持成员数目和类型不变的前提下,定制更有意义的结构体名和/或成员名。
INT32UdwEntryNum:
值名映射表数组条目数
INT32UdwElem:
待解析数值,通常为枚举变量
INT8U*pszDefName:
缺省具名字符串指针,可为空
*输出参数:
NA
*返回值:
INT8U*:
数值所对应的具名字符串当无法解析给定数值时,若pszDefName为空,则返回数值对应的16进制格式字符串;
否则返回pszDefName。
******************************************************************************/
INT8U*NameParser(VOID*pvMap,INT32UdwEntryNum,INT32UdwElem,INT8U*pszDefName)
CHECK_SINGLE_POINTER(pvMap,"
NullPoniter"
INT32UdwEntryIdx=0;
for(dwEntryIdx=0;
dwEntryIdx<
dwEntryNum;
dwEntryIdx++)
T_NAME_PARSER*ptNameParser=(T_NAME_PARSER*)pvMap;
if(dwElem==ptNameParser->
dwElem)
returnptNameParser->
pszName;
//ANSI标准禁止对void指针进行算法操作;
GNU标准则指定void*算法操作与char*一致。
//若考虑移植性,可将pvMap类型改为INT8U*,或定义INT8U*局部变量指向pvMap。
pvMap+=sizeof(T_NAME_PARSER);
if(NULL!
=pszDefName)
returnpszDefName;
staticINT8UszName[12]={0};
//Max:
"
0xFFFFFFFF"
sprintf(szName,"
0x%X"
dwElem);
returnszName;
以下给出NameParser的简单应用示例:
//UNI端口类型值名映射表结构体定义
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]<
TestCase%u>
Result:
%s!
__FUNCTION__,ucTestIndex++,
strcmp("
Unknown"
NameParser(gUniNameMap,UNI_NAM_MAP_NUM,0,"
))?
"
ERROR"
:
OK"
DefName"
NameParser(gUniNameMap,UNI_NAM_MAP_NUM,1,"
NameParser(gUniNameMap,UNI_NAM_MAP_NUM,3,"
NameParser(gUniNameMap,UNI_NAM_MAP_NUM,99,NULL))?
NameParser(gUniNameMap,UNI_NAM_MAP_NUM,255,"
0xABCD"
NameParser(gUniNameMap,UNI_NAM_MAP_NUM,0xABCD,NULL))?
NameParser(NULL,UNI_NAM_MAP_NUM,0xABCD,NULL))?
gUniNameMap在实际项目中有十余个条目,若采用逻辑链实现将非常冗长。
1.2.5取值映射
不同模块间同一参数枚举值取值可能有所差异,需要适配。
此处不再给出普通的switch…case或if…elseif…else结构,而直接示出以下表驱动实现:
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_L
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 精华 知识 驱动 编程 实践