欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    【說在前面的話】

    2022年了,想必已經不會有人對嵌入式開發中“數據結構(Data )”的作用產生疑問了吧?無論你是否心存疑惑,本文都將給你一個完全不同的視角。

    每每說起數據結構,很多人腦海里復現的一定是以下的內容:

    數據結構其實不是一個高大上的名詞,它意外的非常樸實——你也許每天都在用。作為一個新坑,我將在【非常C結構】系列文章中為大家分享很多嵌入式開發中很多“非常”而又“好用”的數據結構。

    【人人都可以學會的“表格”】

    你不必學過所謂的“關系數據庫”也可以理解“表格(Table)”這種數據結構的本質含義。

    在C語言環境中,表格的本質就是結構體數組,即:由結構體組成的數組。這里:

    在嵌入式系統中,表格具有以下特點:

    如果一個需求能夠1)接受上述的特點;或者2)本身就具有上述特點;或者3)部分內容經過改造后可以接受上述特點——那么,就可以使用表格來保存數據了。

    一個典型的例子就是:交互菜單。

    很容易看到,每一級菜單本質上都“可以”是一個表格。

    雖然在很多UI設計工具中(比如LVGL),菜單的內容是在運行時刻動態生成的(用鏈表來實現),但在嵌入式系統中,動態生成表格本身并不是一個“必須使用”的特性,相反,由于產品很多時候功能固定——菜單的內容也是固定的,因此完全沒有必要在運行時刻進行動態生成——這就滿足了表格的“在編譯時刻初始化”的要求。

    采用表格的形式來保存菜單,就獲得了在ROM中保存數據、減少RAM消耗的的優勢。同時,數組的訪問形式又進一步簡化了用戶代碼。

    另外一個常見用到表格的例子是消息地圖( Map),它在通信協議棧解析類的應用中非常常見,在很多結構緊湊功能復雜的中也充當著重要的角色。

    如果你較真起來,菜單也不過消息地圖的一種。表格不是實現消息地圖的唯一方式,但卻是最簡單、最常用、數據存儲密度最高的形式。在后續的例子中,我們就以“消息地圖”為例,深入聊聊表格的使用和優化。

    【表格的定義】一般來說,表格由兩部分構成:

    因此,表格的定義也分為兩個部分:

    記錄的定義一般格式如下:

    typedef?struct?<表格名稱>_item_t  <表格名稱>_item_t;
    struct?<表格名稱>_item_t {????//?每條記錄中的內容};

    這里,第一行的所在行的作用是“前置聲明”;所在行的作用是定義結構體的實際內容。雖然我們完全可以將“前置聲明”和“結構體定義”合二為一,寫作:

    c語言修改結構體中數據_c語言修改結構體中數據_算法與數據結構c語言版答案

    typedef?struct <表格名稱>_item_t {    // 每條記錄中的內容} <表格名稱>_item_t;

    但基于以下原因,我們還是推薦大家堅持第一種寫法:

    以消息地圖為例,一個常見的記錄結構體定義如下:

    typedef?struct?msg_item_t msg_item_t;
    struct?msg_item_t { uint8_t chID; //!< 指令????uint8_t?chAccess;?????????????//!????uint16_t hwValidDataSize; //!< 數據長度要求????bool?(*fnHandler)(msg_item_t?*ptMSG,??????? void?*pData,???????????????????????uint_fast16_t?hwSize);};

    在這個例子中,我們腦補了一個通信指令系統,當我們通過通信前端進行數據幀解析后,獲得了以下的內容:

    為了方便指令解析,我們也需要有針對性的來設計每一條指令的內容,因此,我們加入了 chID 來存儲指令碼;并加入了函數指針來為當前指令綁定一個處理函數;考慮到每條指令所需的最小有效數據長度是已知的,因此,我們通過來記錄這一信息,以便進行信息檢索時快速的做出判斷。具體如何使用,我們后面再說。

    對表格來說,容器是所有記錄的容身之所,可以簡單,但不可以缺席。最簡單的容器就是數組,例如:

    const?msg_item_t?c_tMSGTable[20];

    這里, 類型的數組就是表格的容器,而且我們手動規定了數組中元素的個數。實踐中,我們通常不會像這樣手動的“限定”表格中元素的個數,而是直接“偷懶”——埋頭初始化數組,然后讓編譯器替我們去數數——根據我們初始化元素的個數來確定數組的元素數量,例如:

    const msg_item_t c_tMSGTable[] = {    [0] = {????????.chID?=?0,????????.fnHandler = NULL,    },    [1] = {        ...    },    ...};

    上述寫法是C99語法,不熟悉的小伙伴可以再去翻翻語法書哦。說句題外話,2022年了,連頑固不化的Linux都擁抱C11了,不要再抱著C89規范不放了,起碼用個C99沒問題的。

    上面寫法的好處主要是方便我們偷懶,減少不必要的“數數”過程。那么,我們要如何知道一個表格中數組究竟有多少個元素呢?別慌,我們有 ():

    #ifndef dimof#???dimof(__array)     (sizeof(__array)/sizeof(__array[0]))#endif

    這個語法糖 dimof() 可不是我發明的,不信你問Linux。它的原理很簡單,當我們把數組名稱傳給 dimof() 時,它會:

    通過 () 來獲取整個目標數組的字節尺寸;

    通過 ([0]) 來獲取數組第一個元素的字節尺寸——也就是數組元素的尺寸;

    通過除法獲取數組中元素的個數。

    【表格的訪問(遍歷)】

    由于表格的本質是結構體數組,因此,針對表格最常見的操作就是遍歷(搜索)了。還以前面消息地圖為例子:

    static?volatile?uint8_t?s_chCurrentAccessPermission;
    /*!?\brief?搜索消息地圖,并執行對應的處理程序?*!?\retval?false??消息不存在或者消息處理函數覺得內容無效?*! \retval true 消息得到了正確的處理?*/bool?search_msgmap(uint_fast8_t?chID,?????????????????? void *pData,?????????????????? uint_fast16_t hwSize){????for?(int?n?=?0;?n?????????msg_item_t *ptItem = &c_tMSGTable[n];????????if?(chID?!=?ptItem->chID) {???????? continue;????????}????????if?(!(ptItem->chAccess & s_chCurrentAccessPermission)) {????????????continue;??//!????????}????????if?(hwSize < ptItem->hwSize) {????????????continue; //!< 數據太小了????????}????????if (NULL == ptItem->fnHandler) {????????????continue;??//!????????}????????????????//! 調用消息處理函數????????return?ptItem->fnHandler(ptItem,?pData,?hwSize);????}????????return?false;???//!}

    算法與數據結構c語言版答案_c語言修改結構體中數據_c語言修改結構體中數據

    別看這個函數“很有料”的樣子,其本質其實特別簡單:

    其實上述代碼隱藏了一個特性:就是這個例子中的消息地圖中允許出現chID相同的消息的——這里的技巧是:對同一個chID值的消息,我們可以針對不同的訪問權限(值)來提供不同的處理函數。比如,通信系統中,我們可以設計多種權限和模式,比如:只讀模式、只寫模式、安全模式等等。不同模式對應不同的值。這樣,對哪怕同樣的指令,我們也可以根據當前模式的不同提供不同的處理函數——這只是一種思路,供大家參考。

    【由多實例引入的問題】前面的例子為我們展示表格使用的大體細節,對很多嵌入式應用場景來說,已經完全夠用了。但愛思考的小伙伴一定已經發現了問題:

    如果我的系統中有多個消息地圖(每個消息地圖中消息數量是不同的),我改怎么復用代碼呢?

    為了照顧還一臉懵逼的小伙伴,我把這個問題給大家翻譯翻譯:

    簡而言之,()現在跟某一個消息地圖(數組)綁定死了,如果要讓它支持其它的消息地圖(其它數組),就必須想辦法將其與特定的數組解耦,換句話說,在使用 () 的時候,要提供目標的消息地圖的指針,以及消息地圖中元素的個數。

    一個頭疼醫頭腳疼醫腳的修改方案呼之欲出:

    bool?search_msgmap(msg_item_t?*ptMSGTable,                   uint_fast16_t hwCount,                   uint_fast8_t chID,                   void *pData,                   uint_fast16_t hwSize){    for (int n = 0; n < hwCount; n++) {        msg_item_t *ptItem = &ptMSGTable[n];        if (chID != ptItem->chID) {            continue;        }        ...                //! 調用消息處理函數        return ptItem->fnHandler(ptItem, pData, hwSize);    }        return false;   //!< 沒找到對應的消息}

    假設我們有多個消息地圖,對應不同的工作模式:

    const?msg_item_t?c_tMSGTableUserMode[] = {    ...};const msg_item_t c_tMSGTableSetupMode[] = {    ...};
    const msg_item_t c_tMSGTableDebugMode[] = { ...};
    const msg_item_t c_tMSGTableFactoryMode[] = { ...};

    在使用的時候,可以這樣:

    typedef enum?{????USER_MODE?=?0,????//!????SETUP_MODE,???????//!????DEBUG_MODE,???????//!????FACTORY_MODE,?????//!}?comm_mode_t;
    bool?frame_process_backend(comm_mode_t tWorkMode,?????????????????????????? uint_fast8_t chID,?????????????????????????? void *pData,?????????????????????????? uint_fast16_t hwSize){????bool?bHandled = false; switch (tWorkMode) { case USER_MODE:????????????bHandled = search_msgmap(???????????? c_tMSGTableUserMode, ???????????? dimof(c_tMSGTableUserMode),???????????? chID,???????????? pData,??????????????????????????hwSize);????????????break;???????? case SETUP_MODE: bHandled = search_msgmap( c_tMSGTableSetupMode, dimof(c_tMSGTableUserMode), chID, pData, hwSize); break;?????????... }
    return bHandled;}

    看起來很不錯,對吧?非也非也!早得很呢。

    【表格定義的完全體】前面我們說過,表格的定義分兩個部分:

    其中,關于容器的定義c語言修改結構體中數據,我們說過,數組是容器的最簡單形式。那么容器定義的完全體是怎樣的呢?

    “還是結構體”!

    是的,表格條目的本質是結構體,表格容器的本質也是一個結構體:

    typedef struct <表格名稱>_item_t  <表格名稱>_item_t;
    struct <表格名稱>_item_t { // 每條記錄中的內容};
    typedef struct <表格名稱>_t <表格名稱>_t;
    struct <表格名稱>_t {????uint16_t?hwItemSize;????uint16_t?hwCount;????<表格名稱>_item_t *ptItems;};

    c語言修改結構體中數據_c語言修改結構體中數據_算法與數據結構c語言版答案

    容易發現c語言修改結構體中數據,這里表格容器被定義成了一個叫做 _t 的結構體,其中包含了三個至關重要的元素:

    這個其實是來湊數的,因為32位系統中指針4字節對齊的緣故,2字節的橫豎會產生2字節的氣泡。不理解這一點的小伙伴,可以參考文章《》

    還是以前面消息地圖為例,我們來看看新的容器應該如何定義和使用:

    typedef?struct?msg_item_t?msg_item_t;
    struct msg_item_t { uint8_t chID; //!< 指令 uint8_t chAccess; //!< 訪問權限檢測 uint16_t hwValidDataSize; //!< 數據長度要求 bool (*fnHandler)(msg_item_t *ptMSG, void *pData, uint_fast16_t hwSize);};
    typedef?struct?msgmap_t?msgmap_t;
    struct msgmap_t { uint16_t hwItemSize; uint16_t hwCount;????msg_item_t *ptItems;};
    const msg_item_t c_tMSGTableUserMode[] = { ...};
    const?msgmap_t?c_tMSGMapUserMode = {????.hwItemSize?=?sizeof(msg_item_t),????.hwCount?=?dimof(c_tMSGTableUserMode),????.ptItems = c_tMSGTableUserMode,};

    既然有了定義,() 也要做相應的更新:

    bool?search_msgmap(msgmap_t?*ptMSGMap,                   uint_fast8_t chID,                   void *pData,                   uint_fast16_t hwSize){    for (int n = 0; n < ptMSGMap->hwCount; n++) {        msg_item_t *ptItem = &(ptMSGMap->ptItems[n]);        if (chID != ptItem->chID) {            continue;        }        ...                //! 調用消息處理函數        return ptItem->fnHandler(ptItem, pData, hwSize);    }        return false;   //!< 沒找到對應的消息}

    看到這里,相信很多小伙伴內心是毫無波瀾的……

    “是的……是稍微優雅一點……然后呢?”

    “就這!?就這?!”

    別急,下面才是見證奇跡的時刻。

    【要優雅……】

    在前面的例子中,我們注意到表格的初始化是分兩部分進行的:

    const msg_item_t c_tMSGTableUserMode[] = {    [0] = {        .chID = 0,        .fnHandler = NULL,    },    [1] = {        ...    },    ...};
    const?msgmap_t?c_tMSGMapUserMode = {????.hwItemSize?=?sizeof(msg_item_t),????.hwCount?=?dimof(c_tMSGTableUserMode),????.ptItems = c_tMSGTableUserMode,};

    那么,我們可不可以把它們合二為一呢?這樣:

    要做到這一點,我們可以使用一個類似“匿名數組”的功能:

    我們想象中的樣子:

    const msgmap_t c_tMSGMapUserMode = {    .hwItemSize = sizeof(msg_item_t),    .hwCount = dimof(c_tMSGTableUserMode),    .ptItems = const msg_item_t c_tMSGTableUserMode[] = {          [0] = {              .chID = 0,              .fnHandler = NULL,          },          [1] = {              ...          },          ...      },};

    使用“匿名數組”后的樣子(也就是刪除數組名稱后的樣子):

    const msgmap_t c_tMSGMapUserMode = {    .hwItemSize = sizeof(msg_item_t),    .hwCount = dimof(c_tMSGTableUserMode),????.ptItems?=?(msg_item_t?[]){          [0] = {              .chID = 0,              .fnHandler = NULL,          },          [1] = {              ...          },          ...      },};

    算法與數據結構c語言版答案_c語言修改結構體中數據_c語言修改結構體中數據

    其實,這不是什么“黑魔法”,而是一個廣為使用的GNU擴展,被稱為“復合式描述( )”,本質上就是一種以“省略”數組或結構體名稱的方式來初始化數組或結構體的語法結構。具體語法介紹,小伙伴們可以參考這篇文章《》。

    眼尖的小伙伴也許已經發現了問題:既然我們省略了變量名,那么如何通過 dimof() 來獲取數組元素的個數呢?

    少俠好眼力!

    解決方法不僅有,而且簡單粗暴:

    const msgmap_t c_tMSGMapUserMode = {    .hwItemSize = sizeof(msg_item_t),        .hwCount = dimof((msg_item_t []){          [0] = {              .chID = 0,              .fnHandler = NULL,          },          [1] = {              ...          },          ...??????}),      ????.ptItems?=?(msg_item_t?[]){          [0] = {              .chID = 0,              .fnHandler = NULL,          },          [1] = {              ...          },          ...      },};

    所以說?……

    為了優雅的初始化……

    我們要把同樣的內容寫兩次?!!

    手寫的確挺愚蠢,但宏可以啊!

    #define __impl_table(__item_type, ...)                   \????.ptItems?=?(__item_type?[])?{??????????????????????? \        __VA_ARGS__                                      \    },                                                   \????.hwCount?=?sizeof((__item_type?[])?{?__VA_ARGS__?})??\             / sizeof(__item_type),                      \    .hwItemSize = sizeof(__item_type)
    #define?impl_table(__item_type,?...)???? \ __impl_table(__item_type,?__VA_ARGS__)

    借助上面的語法糖,我們可以輕松的將整個表格的初始化變得簡單優雅:

    const msgmap_t c_tMSGMapUserMode = {    impl_table(msg_item_t,           [0] = {              .chID = 0,              .fnHandler = NULL,          },          [1] = {              ...          },          ...    ),};

    這下舒服了吧?

    【禁止套娃……】還記得前面多實例的例子吧?

    const msg_item_t c_tMSGTableUserMode[] = {    ...};const msg_item_t c_tMSGTableSetupMode[] = {    ...};
    const msg_item_t c_tMSGTableDebugMode[] = { ...};
    const msg_item_t c_tMSGTableFactoryMode[] = { ...};

    現在當然就要改為如下的形式了:

    const msgmap_t c_tMSGMapUserMode = {    impl_table(msg_item_t,         ...????),};
    const msgmap_t c_tMSGMapSetupMode = { impl_table(msg_item_t, ... ),};
    const msgmap_t c_tMSGMapDebugMode = { impl_table(msg_item_t, ... ),};
    const msgmap_t c_tMSGMapFactoryMode = { impl_table(msg_item_t, ... ),};

    但……它們不都是 類型的么?為啥不做一個數組呢?

    c語言修改結構體中數據_c語言修改結構體中數據_算法與數據結構c語言版答案

    typedef enum {    USER_MODE = 0,    //!< 普通的用戶模式    SETUP_MODE,       //!< 出廠后的安裝模式    DEBUG_MODE,       //!< 工程師專用的調試模式    FACTORY_MODE,     //!< 最高權限的工廠模式} comm_mode_t;
    const?msgmap_t?c_tMSGMap[] = { [USER_MODE] = { impl_table(msg_item_t, ... ), }, [SETUP_MODE] = { impl_table(msg_item_t, ... ), }, [DEBUG_MODE] = { impl_table(msg_item_t, ... ), }, [FACTORY_MODE] = { impl_table(msg_item_t, ... ), },};

    是不是有點意思了?再進一步,我們完全可以做一個新的表格,表格的元素就是 呀?

    typedef?struct?cmd_modes_t?cmd_modes_t;
    struct cmd_modes_t { uint16_t hwItemSize; uint16_t hwCount; msgmap_t *ptItems;};

    然后就可以開始套娃咯:

    const?cmd_modes_t?c_tCMDModes?=?{????impl_table(msgmap_t,????    [USER_MODE] = {            impl_table(msg_item_t,                 [0] = {                    .chID = 0,                    .fnHandler = NULL,                },                [1] = {                    ...                },                ...            ),        },        [SETUP_MODE] = {            impl_table(msg_item_t,                 ...            ),        },        [DEBUG_MODE] = {            impl_table(msg_item_t,                 ...            ),        },        [FACTORY_MODE] = {            impl_table(msg_item_t,                 ...            ),        },????),};

    【差異化……】

    在前面的例子中,我們可以根據新的定義方式更新函數 d() 函數:


    extern const cmd_modes_t c_tCMDModes;
    bool frame_process_backend(comm_mode_t tWorkMode, uint_fast8_t chID, void *pData, uint_fast16_t hwSize){ bool bHandled = false; ????if?(tWorkMode?> FACTORY_MODE) {???? return false;????}????????return search_msgmap( &(c_tCMDModes.ptItems[tWorkMode]),? chID, pData,??????????????????????????hwSize);}

    是不是特別優雅?

    把容器定義成結構體還有一個好處,就是可以給表格更多的差異化,這意味著,除了條目數組相關的內容外,我們還可以放入其它東西,比如:現有的 d() 為每一個消息地圖()都使用相同的處理函數 () ,這顯然缺乏差異化的可能性。如果每個消息地圖都有可能有自己的特殊處理函數怎么辦呢?

    為了實現這一功能,我們可以對 進行擴展:

    typedef struct msgmap_t msgmap_t;
    struct msgmap_t { uint16_t hwItemSize; uint16_t hwCount; msg_item_t *ptItems;????bool?(*fnHandler)(msgmap_t?*ptMSGMap, uint_fast8_t chID, void *pData,??????????????????????uint_fast16_t?hwSize);};

    則初始化的時候,我們就可以給每個消息地圖指定一個不同的處理函數:

    extern     bool?msgmap_user_mode_handler(msgmap_t?*ptMSGMap,                      uint_fast8_t chID,                      void *pData,                      uint_fast16_t hwSize);
    extern bool msgmap_debug_mode_handler(msgmap_t *ptMSGMap, uint_fast8_t chID, void *pData, uint_fast16_t hwSize);
    const cmd_modes_t c_tCMDModes = { impl_table(msgmap_t, [USER_MODE] = { impl_table(msg_item_t, ????????????????... ), .fnHandler = &msgmap_user_mode_handler, }, [SETUP_MODE] = { impl_table(msg_item_t, ... ),????????????.fnHandler?=?NULL;?//! }, [DEBUG_MODE] = { impl_table(msg_item_t, ... ), .fnHandler = &msgmap_debug_mode_handler, }, [FACTORY_MODE] = { impl_table(msg_item_t, ... ),????????????//.fnHandler = NULL 什么都不寫,就是NULL(0) }, ),};

    此時,我們再更新d()函數,讓上述差異化功能成為可能:


    bool frame_process_backend(comm_mode_t tWorkMode, uint_fast8_t chID, void *pData, uint_fast16_t hwSize){ bool bHandled = false;????msgmap_t?*ptMSGMap?= c_tCMDModes.ptItems[tWorkMode];????if?(tWorkMode?> FACTORY_MODE) {???? return false;????}????????//!?調用每個消息地圖自己的處理程序????if (NULL != ptMSGMap->fnHandler) {?????????return?ptMSGMap->fnHandler(ptMSGMap,? chID, pData, hwSize);????}????//!?默認的消息地圖處理程序????return search_msgmap( ptMSGMap, chID, pData, hwSize);}

    【說在后面的話】啥都不說了……你們看著辦吧。我們下期再見。

    —— The End——

    往期推薦

    點擊上方名片關注我

    你點的每個好看,我都認真當成了喜歡

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有