0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

詳解C語言的驅(qū)動法編程

STM32嵌入式開發(fā) ? 來源:STM32嵌入式開發(fā) ? 作者:STM32嵌入式開發(fā) ? 2022-04-18 10:26 ? 次閱讀

數(shù)據(jù)壓倒一切。如果選擇了正確的數(shù)據(jù)結(jié)構(gòu)并把一切組織的井井有條,正確的算法就不言自明。編程的核心是數(shù)據(jù)結(jié)構(gòu),而不是算法。

——Rob Pike

說明

本文基于這樣的認識:數(shù)據(jù)是易變的,邏輯是穩(wěn)定的。本文例舉的編程實現(xiàn)多為代碼片段,但不影響描述的完整性。本文例舉的編程雖然基于C語言,但其編程思想也適用于其他語言。此外,本文不涉及語言相關(guān)的運行效率討論。

1 概念提出

所謂表驅(qū)動法(Table-Driven Approach)簡而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫的一種體現(xiàn)。 根據(jù)字典中的部首檢字表查找讀音未知的漢字就是典型的表驅(qū)動法,即以每個字的字形為依據(jù),計算出一個索引值,并映射到對應(yīng)的頁數(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';}
當(dāng)然也可以用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)];
像這樣直接將變量當(dāng)作下數(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)容。

1.1 查表方式

常用的查表方式有直接查找、索引查找和分段查找等。

1.1.1 直接查找

即直接通過數(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ù)量大小可提前預(yù)測的情況。 對于過于復(fù)雜和龐大的判斷,可將數(shù)據(jù)存為文件,需要時加載文件初始化數(shù)組,從而在不修改程序的情況下調(diào)整里面的數(shù)值。 有時,訪問之前需要先進行一次鍵值轉(zhuǎn)換。如表驅(qū)動法表示端口忙閑時,需將槽位端口號映射為全局編號。所生成的端口數(shù)目大小的數(shù)組,其下標對應(yīng)全局端口編號,元素值表示相應(yīng)端口的忙閑狀態(tài)。

1.1.2 索引查找

有時通過一次鍵值轉(zhuǎn)換,依然無法把數(shù)據(jù)(如英文單詞等)轉(zhuǎn)為鍵值。此時可將轉(zhuǎn)換的對應(yīng)關(guān)系寫到一個索引表里,即索引訪問。 如現(xiàn)有100件商品,4位編號,范圍從0000到9999。此時只需要申請一個長度為100的數(shù)組,且對應(yīng)2位鍵值。但將4位的編號轉(zhuǎn)換為2位的鍵值,可能過于復(fù)雜或沒有規(guī)律,最合適的方法是建立一個保存該轉(zhuǎn)換關(guān)系的索引表。采用索引訪問既節(jié)省內(nèi)存,又方便維護。比如索引A表示通過名稱訪問,索引B表示通過編號訪問。

1.1.3 分段查找

通過確定數(shù)據(jù)所處的范圍確定分類(下標)。有的數(shù)據(jù)可分成若干區(qū)間,即具有階梯性,如分數(shù)等級。此時可將每個區(qū)間的上限(或下限)存到一個表中,將對應(yīng)的值存到另一表中,通過第一個表確定所處的區(qū)段,再由區(qū)段下標在第二個表里讀取相應(yīng)數(shù)值。注意要留意端點,可用二分法查找,另外可考慮通過索引方法來代替。 如根據(jù)分數(shù)查績效等級:

	
#define MAX_GRADE_LEVEL   (INT8U)5DOUBLE 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 < MAX_GRADE_LEVEL; ucLevel++)    {        if(dScore < aRangeLimit[ucLevel])            return paGrades[ucLevel];    }    return paGrades[0];}
上述兩張表(數(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 < MAX_GRADE_LEVEL; ucLevel++)    {        if(dScore < gGradeMap[ucLevel].aRangeLimit)            return gGradeMap[ucLevel].pszGrade;    }    return gGradeMap[0].pszGrade;}
該表結(jié)構(gòu)已具備的數(shù)據(jù)庫的雛形,并可擴展支持更為復(fù)雜的數(shù)據(jù)。其查表方式通常為索引查找,偶爾也為分段查找;當(dāng)索引具有規(guī)律性(如連續(xù)整數(shù))時,退化為直接查找。 使用分段查找法時應(yīng)注意邊界,將每一分段范圍的上界值都考慮在內(nèi)。找出所有不在最高一級范圍內(nèi)的值,然后把剩下的值全部歸入最高一級中。有時需要人為地為最高一級范圍添加一個上界。 同時應(yīng)小心不要錯誤地用“<”來代替“<=”。要保證循環(huán)在找出屬于最高一級范圍內(nèi)的值后恰當(dāng)?shù)亟Y(jié)束,同時也要保證恰當(dāng)處理范圍邊界。

1.2 實戰(zhàn)示例

本節(jié)多數(shù)示例取自實際項目。表形式為一維數(shù)組、二維數(shù)組和結(jié)構(gòu)體數(shù)組;表內(nèi)容有數(shù)據(jù)、字符串和函數(shù)指針。基于表驅(qū)動的思想,表形式和表內(nèi)容可衍生出豐富的組合。

1.2.1 字符統(tǒng)計

問題:統(tǒng)計用戶輸入的一串?dāng)?shù)字中每個數(shù)字出現(xiàn)的次數(shù)。 普通解法主體代碼如下:

	
INT32U aDigitCharNum[10] = {0}; /* 輸入字符串中各數(shù)字字符出現(xiàn)的次數(shù) */INT32U dwStrLen = strlen(szDigits);
INT32U dwStrIdx = 0;for(; dwStrIdx < dwStrLen; dwStrIdx++){    switch(szDigits[dwStrIdx])    {        case '1':            aDigitCharNum[0]++;            break;        case '2':            aDigitCharNum[1]++;            break;        //... ...        case '9':            aDigitCharNum[8]++;            break;    }}
這種解法的缺點顯而易見,既不美觀也不靈活。其問題關(guān)鍵在于未將數(shù)字字符與數(shù)組aDigitCharNum下標直接關(guān)聯(lián)起來。 以下示出更簡潔的實現(xiàn)方式:

	
for(; dwStrIdx < dwStrLen; dwStrIdx++){    aDigitCharNum[szDigits[dwStrIdx] - '0']++;}
上述實現(xiàn)考慮到0也為數(shù)字字符。該解法也可擴展至統(tǒng)計所有ASCII可見字符。

1.2.2 月天校驗

問題:對給定年份和月份的天數(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ù),下標對應(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;}

1.2.3 名稱構(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語言中更簡潔的實現(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 < ucSvrNameNum; ucIdx++)    {        if(1 == GET_BIT(wSvrType, ucIdx))            strcat(pszSvrType, paSvrNames[ucIdx]);    }}
新的實現(xiàn)將數(shù)據(jù)和邏輯分離,維護起來非常方便。只要邏輯(規(guī)則)不變,則唯一可能的改動就是數(shù)據(jù)(paSvrNames)。

1.2.4 值名解析

問題:根據(jù)枚舉變量取值輸出其對應(yīng)的字符串,如PORT_FE(1)輸出“Fe”。
//值名映射表結(jié)構(gòu)體定義,用于數(shù)值解析器typedef struct{    INT32U dwElem;    //待解析數(shù)值,通常為枚舉變量    CHAR*  pszName;   //指向數(shù)值所對應(yīng)解析名字符串的指針}T_NAME_PARSER;
/******************************************************************************* 函數(shù)名稱:  NameParser* 功能說明:  數(shù)值解析器,將給定數(shù)值轉(zhuǎn)換為對應(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ù)值所對應(yīng)的具名字符串             當(dāng)無法解析給定數(shù)值時,若pszDefName為空,則返回數(shù)值對應(yīng)的16進制格式             字符串;否則返回pszDefName。******************************************************************************/INT8U *NameParser(VOID *pvMap, INT32U dwEntryNum, INT32U dwElem, INT8U* pszDefName){    CHECK_SINGLE_POINTER(pvMap, "NullPoniter");
    INT32U dwEntryIdx = 0;    for(dwEntryIdx = 0; dwEntryIdx < dwEntryNum; dwEntryIdx++)    {        T_NAME_PARSER *ptNameParser = (T_NAME_PARSER *)pvMap;        if(dwElem == ptNameParser->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的簡單應(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在實際項目中有十余個條目,若采用邏輯鏈實現(xiàn)將非常冗長。
		

1.2.5 取值映射

問題:不同模塊間同一參數(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 < num; ii++)    {        if(vPortState == s_CesLoop[ii].loopMEState)            return s_CesLoop[ii].loopMIBState;    }    return e_ds1_looptype_noloop;}
相應(yīng)地,從loopMIBState映射到loopMEState需要定義一個ConvertLoopMIBStateToMEState函數(shù)。更進一步,所有類似的一對一映射關(guān)系都必須如上的映射(轉(zhuǎn)換)函數(shù),相當(dāng)繁瑣。 事實上,從抽象層面看,該映射關(guān)系非常簡單。提取共性后定義帶參數(shù)宏,如下所示:

	
/*********************************************************** 功能描述:進行二維數(shù)組映射表的一對一映射,用于參數(shù)適配* 參數(shù)說明:map        -- 二維數(shù)組映射表            elemSrc    -- 映射源,即待映射的元素值            elemDest   -- 映射源對應(yīng)的映射結(jié)果            direction  -- 映射方向字節(jié),表示從數(shù)組哪列映射至哪列。                          高4位對應(yīng)映射源列,低4位對應(yīng)映射結(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 < ucMapNum; ucMapIdx++)     {         if((elemSrc) == map[ucMapIdx][((direction)&0xF0)>>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 < ucNum; ucIdx++)    {       if((dwJitterBuffer == gCesQueueSizeMap[ucIdx].dwJitterBuffer) &&          (dwFramePerPkt == gCesQueueSizeMap[ucIdx].dwFramePerPkt))       {            return gCesQueueSizeMap[ucIdx].dwQueueSize;       }    }
    return CES_DEFAULT_MAX_QUEUESIZE;}

1.2.6 版本控制

問題:控制OLT與ONU之間的版本協(xié)商。ONU本地設(shè)置三比特控制字,其中bit2(MSB)~bit0(LSB)分別對應(yīng)0x21、0x30和0xAA版本號;且bitX為0表示上報對應(yīng)版本號,bitX為1表示不上報對應(yīng)版本號。其他版本號如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時上報對應(yīng)版本,Bit-X為1時屏蔽對應(yīng)版本。* CTRL_VERS_NUM:  可控版本個數(shù)。* CTRL_CODE_NUM:  控制字個數(shù)。與CTRL_VERS_NUM有關(guān)。* gOamVerCtrlMap: 版本控制字數(shù)組。行對應(yīng)控制字,列對應(yīng)可控版本。                  元素值為0時不上報對應(yīng)版本,元素值非0時上報該元素值。* Note: 該數(shù)組旨在實現(xiàn)“數(shù)據(jù)與控制隔離”。后續(xù)若要新增可控版本,只需修改                  -- CTRL_VERS_NUM                  -- gOamVerCtrlMap新增行(控制字)                  -- gOamVerCtrlMap新增列(可控版本)**********************************************************************/#define CTRL_VERS_NUM    3#define CTRL_CODE_NUM    (1<u8_t gOamVerCtrlMap[CTRL_CODE_NUM][CTRL_VERS_NUM] = { /* Ver21         Ver30        VerAA */    {0x21,         0x30,        0xaa},    /*gOamCtrlCode = 0*/    {0x21,         0x30,          0 },    /*gOamCtrlCode = 1*/    {0x21,           0,         0xaa},    /*gOamCtrlCode = 2*/    {0x21,           0,           0 },    /*gOamCtrlCode = 3*/    {  0,          0x30,        0xaa},    /*gOamCtrlCode = 4*/    {  0,          0x30,          0 },    /*gOamCtrlCode = 5*/    {  0,            0,         0xaa},    /*gOamCtrlCode = 6*/    {  0,            0,           0 }     /*gOamCtrlCode = 7*/};#define INFO_TYPE_VERS_LEN    7  /* InfoType + Length + OUI + ExtSupport + Version */
u8_t verIdx = 0;u8_t index = 0;for(verIdx = 0; verIdx < CTRL_VERS_NUM; verIdx++){    if(gOamVerCtrlMap[gOamCtrlCode][verIdx] != 0)    {        vosMemCpy(&pstSendTlv->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 消息處理

問題:終端輸入不同的打印命令,調(diào)用相應(yīng)的打印函數(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 << FUNCTION_Oam);}void nologOam(void){    g_log_control[0] &= ~(0x01 << FUNCTION_Oam);}//... ...void logExec(char *name, INT8U enable){    CtcOamLog(FUNCTION_Oam,"log %s %d
",name,enable);    if (enable == 1) /*log*/    {        if (strcasecmp(name,"all") == 0) { /*字符串比較,不區(qū)分大小寫*/            logall();        } else if (strcasecmp(name,"oam") == 0) {            logOam();        } else if (strcasecmp(name,"pon") == 0) {            logPon();        //... ...        } else if (strcasecmp(name,"version") == 0) {            logVersion();    }    else if (enable == 0) /*nolog*/    {        if (strcasecmp(name,"all") == 0) {            noanylog();        } else if (strcasecmp(name,"oam") == 0) {            nologOam();        } else if (strcasecmp(name,"pon") == 0) {            nologPon();        //... ...        } else if (strcasecmp(name,"version") == 0) {            nologVersion();    }    else    {        printf("bad log para
");    }}
以下示出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 < gOamLogMapNum; ucIdx++)    {        if((ucSwitch == gOamLogMap[ucIdx].eLogMode) &&           (!strcasecmp(pszName, gOamLogMap[ucIdx].pszLogCls));        {            gOamLogMap[ucIdx].fnLogHandler();            return;        }    }    if(ucIdx == gOamLogMapNum)    {        printf("Unknown LogClass(%s) or LogMode(%d)!
", pszName, ucSwitch);        return;    }}
這種表驅(qū)動消息處理實現(xiàn)的優(yōu)點如下: 1.增強可讀性,消息如何處理從表中一目了然。2.增強可擴展性。更容易修改,要增加新的消息,只要修改數(shù)據(jù)即可,不需要修改流程。3.降低復(fù)雜度。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達到控制復(fù)雜度的目標。4.主干清晰,代碼重用。 若各索引為順序枚舉值,則建立多維數(shù)組(每維對應(yīng)一個索引),根據(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)絡(luò)日志 */    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分別對應(yīng)E_LOG_TYPE各枚舉值(除LOG_ALL外)。 *           BitX為0時關(guān)閉日志類型對應(yīng)的日志功能,BitX為1時則予以打開。 * 變量范圍:該變量為四字節(jié)整型靜態(tài)全局變量,即支持32種日志類型。 * 訪問說明:通過GetOmciLogCtrl/SetOmciLogCtrl/OmciLogCtrl函數(shù)訪問/設(shè)置控制字。 *****************************************************************************/static INT32U gOmciLogCtrl = 0;
//日志類型字符串?dāng)?shù)組,下標為各字符串所對應(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 < ucLogTypeNameNum; ucIdx++)    {        if(0 == strncasecmp(pszLogType, paLogTypeName[ucIdx], LOG_TYPE_CMP_LEN))        {            SetSpecificLogCtrl(ucIdx, ucLogSwitch);            printf("LogType: %s, LogSwitch: %s
", paLogTypeName[ucIdx],                   (1==ucLogSwitch)?"On":"Off");            return;        }    }
    OmciLogHelp();}

2 編程思想

表驅(qū)動法屬于數(shù)據(jù)驅(qū)動編程的一種,其核心思想在《Unix編程藝術(shù)》和《代碼大全2》中均有闡述。兩者均認為人類閱讀復(fù)雜數(shù)據(jù)結(jié)構(gòu)遠比復(fù)雜的控制流程容易,即相對于程序邏輯,人類更擅長于處理數(shù)據(jù)。 本節(jié)將由Unix設(shè)計原則中的“分離原則”和“表示原則”展開。

分離原則:策略同機制分離,接口同引擎分離

機制即提供的功能;策略即如何使用功能。 策略的變化要遠遠快于機制的變化。將兩者分離,可以使機制相對保持穩(wěn)定,而同時支持策略的變化。 代碼大全中提到“隔離變化”的概念,以及設(shè)計模式中提到的將易變化的部分和不易變化的部分分離也是這個思路。

表示原則:把知識疊入數(shù)據(jù)以求邏輯質(zhì)樸而健壯

即使最簡單的程序邏輯讓人類來驗證也很困難,但就算是很復(fù)雜的數(shù)據(jù),對人類來說,還是相對容易推導(dǎo)和建模的。數(shù)據(jù)比編程邏輯更容易駕馭。在復(fù)雜數(shù)據(jù)和復(fù)雜代碼中選擇,寧可選擇前者。更進一步,在設(shè)計中,應(yīng)該主動將代碼的復(fù)雜度轉(zhuǎn)移到數(shù)據(jù)中去(參考“版本控制”)。 在“消息處理”示例中,每個消息處理的邏輯不變,但消息可能是變化的。將容易變化的消息和不容易變化的查找邏輯分離,即“隔離變化”。此外,該例也體現(xiàn)消息內(nèi)部的處理邏輯(機制)和不同的消息處理(策略)分離。 數(shù)據(jù)驅(qū)動編程可以應(yīng)用于: 1.函數(shù)級設(shè)計,如本文示例。2.程序級設(shè)計,如用表驅(qū)動法實現(xiàn)狀態(tài)機。3.系統(tǒng)級設(shè)計,如DSL。 注意,數(shù)據(jù)驅(qū)動編程不是全新的編程模型,只是一種設(shè)計思路,在Unix/Linux開源社區(qū)應(yīng)用很多。數(shù)據(jù)驅(qū)動編程中,數(shù)據(jù)不但表示某個對象的狀態(tài),實際上還定義程序的流程,這點不同于面向?qū)ο笤O(shè)計中的數(shù)據(jù)“封裝”。

3 附錄

3.1 網(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)絡(luò)游戲開發(fā)過程中很常用,但是少有人專門提到這個詞。 數(shù)據(jù)驅(qū)動編程有很多名字:元編程,解釋器/虛擬機,LOP/微語言/DSL等。包括聲明式編程、標記語言、甚至所見即所得的拖放控件,都算是數(shù)據(jù)驅(qū)動編程的一種吧。 數(shù)據(jù)驅(qū)動編程可以幫助處理復(fù)雜性,和結(jié)構(gòu)化編程、OO 均可相容。(正交的角度)將變和不變的部分分離,策略和機制分離,由此聯(lián)想到的還有:(數(shù)據(jù)和代碼的分離,微語言和解釋器的分離,被生成代碼和代碼生成器的分離);更近一步:(微內(nèi)核插件式體系結(jié)構(gòu)) 元編程應(yīng)該說是更加泛化的數(shù)據(jù)驅(qū)動編程,元編程不是新加入一個間接層,而是退居一步,使得當(dāng)前的層變成一個間接層。元編程分為靜態(tài)元編程(編譯時)和動態(tài)元編程(運行時),靜態(tài)元編程本質(zhì)上是一種 代碼生成技術(shù)或者編譯器技術(shù);動態(tài)元編程一般通過解釋器(或虛擬機)加以實現(xiàn)。 數(shù)據(jù)驅(qū)動編程當(dāng)然也不應(yīng)該說是“反抽象的”,但的確與“OO抽象”的思維方式是迥然不同,涇渭分明的,如TAOUP一書中所述:“在Unix的模塊化傳統(tǒng)和圍繞OO語言發(fā)展起來的使用模式之間,存在著緊張的對立關(guān)系”應(yīng)該說數(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,要可視化編程——當(dāng)然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——向上拋出之道——將復(fù)雜的易變的邏輯作為數(shù)據(jù)或更高層代碼拋給上層!

3.2 函數(shù)指針

“消息處理”一節(jié)示例中的函數(shù)指針有點插件結(jié)構(gòu)的味道。可對這些插件進行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對事件處理函數(shù)的查找又是隔離的(隔離變化)。 函數(shù)指針非常有用,但使用時需注意其缺陷:無法檢查參數(shù)(parameter)和返回值(return value)的類型。因為函數(shù)已經(jīng)退化成指針,而指針不攜帶這些類型信息。缺少類型檢查,當(dāng)參數(shù)或返回值不一致時,可能會造成嚴重的錯誤。 例如,定義三個函數(shù),分別具有兩個參數(shù):
int max(int x, int y)  {  return x>y?x:y;  }int min(int x, int y)  {  return xint add(int x, int y)  {  return x+y;  }
而處理函數(shù)卻定義為:
intprocess(intx,inty,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ù)指針時,一定要小心"類型陷阱"。

原文標題:相見恨晚!C語言的驅(qū)動法編程詳細解析(超多代碼案例)

文章出處:【微信公眾號:STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

審核編輯:湯梓紅


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4576

    瀏覽量

    92343
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7581

    瀏覽量

    135587
  • 編程
    +關(guān)注

    關(guān)注

    88

    文章

    3544

    瀏覽量

    93474

原文標題:相見恨晚!C語言的驅(qū)動法編程詳細解析(超多代碼案例)

文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    C語言-文件編程

    這篇文章介紹C語言的文件編程函數(shù),案例代碼是在Linux環(huán)境下運行測試的分別介紹了C語言標準庫的文件編程
    的頭像 發(fā)表于 09-09 11:33 ?1948次閱讀

    STM8的C語言編程說明

    STM8的C語言編程說明STM8的C語言編程說明STM8的C
    發(fā)表于 12-10 16:19 ?37次下載

    C語言入門經(jīng)典-C語言編程

    C語言入門經(jīng)典-C語言編程,感興趣的可以看看哦。
    發(fā)表于 08-16 18:54 ?121次下載

    PLC編程語言_操作指令_使用步驟詳解

    PLC編程語言_操作指令_使用步驟詳解,感興趣的小伙伴們可以瞧一瞧。
    發(fā)表于 05-12 10:36 ?0次下載

    ARM_C語言程序設(shè)計詳解

    ARM_C語言程序設(shè)計詳解
    發(fā)表于 10-27 15:39 ?32次下載
    ARM_<b class='flag-5'>C</b><b class='flag-5'>語言</b>程序設(shè)計<b class='flag-5'>詳解</b>

    C語言的精髓——指針詳解

    C語言的精髓——指針詳解
    發(fā)表于 11-30 14:43 ?17次下載

    plc與C語言有什么關(guān)系_plc可以用c語言編程嗎_plc的C語言編程詳解

    PLC為可編程邏輯控制器(Programmable Logic Controller)的首字母縮寫。是一種面向工業(yè)控制的微型計算機系統(tǒng)。PLC有自己的編程語言,不能用c
    的頭像 發(fā)表于 02-06 14:55 ?7.3w次閱讀

    單片機C語言和匯編語言混合編程實例詳解

    單片機C語言和匯編語言混合編程實例詳解
    發(fā)表于 08-16 09:50 ?225次下載

    C語言驅(qū)動編程的代碼案例

    是穩(wěn)定的。 本文例舉的編程實現(xiàn)多為代碼片段,但不影響描述的完整性。 本文例舉的編程雖然基于C語言,但其編程思想也適用于其他
    的頭像 發(fā)表于 10-12 09:21 ?1937次閱讀

    單片機c語言+編程c語言_CC ++編程語言有什么區(qū)別?

    單片機c語言+編程c語言C & C++ ar
    發(fā)表于 11-23 17:51 ?20次下載
    單片機<b class='flag-5'>c</b><b class='flag-5'>語言</b>+<b class='flag-5'>編程</b><b class='flag-5'>c</b><b class='flag-5'>語言</b>_<b class='flag-5'>C</b>和<b class='flag-5'>C</b> ++<b class='flag-5'>編程</b><b class='flag-5'>語言</b>有什么區(qū)別?

    單片機c語言+編程c語言_C編程語言簡介

    單片機c語言+編程c語言In this tutorial you will get basic introduction to
    發(fā)表于 11-29 10:21 ?38次下載
    單片機<b class='flag-5'>c</b><b class='flag-5'>語言</b>+<b class='flag-5'>編程</b><b class='flag-5'>c</b><b class='flag-5'>語言</b>_<b class='flag-5'>C</b><b class='flag-5'>編程</b><b class='flag-5'>語言</b>簡介

    C語言驅(qū)動編程詳細解析分享

    使用表驅(qū)動時需要關(guān)注兩個問題:一是如何查表,從表中讀取正確的數(shù)據(jù);二是表里存放什么,如數(shù)值或函數(shù)指針。前者參見1.1節(jié)“查表方式”內(nèi)容,后者參見1.2節(jié)“實戰(zhàn)示例”內(nèi)容。
    的頭像 發(fā)表于 02-20 09:20 ?583次閱讀

    基于C語言驅(qū)動編程代碼分析

    所謂表驅(qū)動(Table-Driven Approach)簡而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫的一種體現(xiàn)。
    發(fā)表于 02-20 09:20 ?326次閱讀

    C語言驅(qū)動編程詳細解析

    所謂表驅(qū)動(Table-Driven Approach)簡而言之就是用查表的方法獲取數(shù)據(jù)。此處的“表”通常為數(shù)組,但可視為數(shù)據(jù)庫的一種體現(xiàn)。
    的頭像 發(fā)表于 03-08 10:46 ?803次閱讀

    PLC編程語言C語言的區(qū)別

    在工業(yè)自動化和計算機編程領(lǐng)域中,PLC(可編程邏輯控制器)編程語言C語言各自扮演著重要的角色。
    的頭像 發(fā)表于 06-14 17:11 ?2063次閱讀