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

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

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

宏的高級用法

工程師 ? 來源:嵌入式軟件實(shí)戰(zhàn)派 ? 作者:嵌入式軟件實(shí)戰(zhàn)派 ? 2020-09-15 15:59 ? 次閱讀

對于條件/分支處理的程序設(shè)計(jì),我們慣性地會(huì)選擇switch-case或者if-else,這也是C語言老師當(dāng)初教的。以下,我們用一個(gè)播放器的例子來說明,要實(shí)現(xiàn)的功能如下:

收到用戶操作播放器命令請求,如“播放”、“暫?!钡龋绦蛞獙γ钭鲄^(qū)分;

針對不同的命令請求,作相應(yīng)的處理;

輸出必要的輔助信息。

首先,將命令定義成enum類型:

enum { CMD_PLAY, CMD_PAUSE, CMD_STOP, CMD_PLAY_NEXT, CMD_PLAY_PREV, };

然后,用switch-case的分支處理:

switch(cmd) { case CMD_PLAY: // handle play command break; case CMD_PAUSE: // handle pause command break; case CMD_STOP: // handle stop command break; case CMD_PLAY_NEXT: // handle play next command break; case CMD_PLAY_PREV: // handle play previous command break; default: break; }

實(shí)際上,這也沒什么毛病。但是,時(shí)間長了,需求不斷變更,程序不斷迭代,這個(gè)switch-case會(huì)變得非常冗長而很難維護(hù)。你不相信?我曾經(jīng)見到過》1000行的類似這樣的代碼。如果讓你接手維護(hù)這樣的代碼,你內(nèi)心會(huì)不會(huì)狂奔著萬千***?

但是,我不敢更改這個(gè)祖?zhèn)鞯膕witch-case啊,那么小心翼翼地將這些命令處理封裝成函數(shù)。像這樣:

#define FUNC_IN() printf(“enter %s \r\n”, __FUNCTION__) void func_cmd_play(void* p) { FUNC_IN(); } void func_cmd_pause(void* p) { FUNC_IN(); } void func_cmd_stop(void* p) { FUNC_IN(); } void func_cmd_play_next(void* p) { FUNC_IN(); } void func_cmd_play_prev(void* p) { FUNC_IN(); } void player_cmd_handle(int cmd, void* p) { switch(cmd) { case CMD_PLAY: func_cmd_play(p); break; case CMD_PAUSE: func_cmd_pause(p); break; case CMD_STOP: func_cmd_stop(p); break; case CMD_PLAY_NEXT: func_cmd_play_next(p); break; case CMD_PLAY_PREV: func_cmd_play_prev(p); break; default: break; } }

后來,甲方還是不斷地更改需求,導(dǎo)致播放器的命令越來越多,幾十個(gè)上百個(gè)了……痛定思痛,我——要——改——革??!

解放switch-case/if-else

腦子里想來想去,度娘上翻來翻去,于是定義了個(gè)結(jié)構(gòu)體:

typedef void(*pFunc)(void* p); typedef struct { tCmd cmd; pFunc func; }tPlayerStruct; tPlayerStruct player_cmd_func[] = { {CMD_PLAY, func_cmd_play) }, {CMD_PAUSE, func_cmd_pause) }, {CMD_STOP, func_cmd_stop) }, {CMD_PLAY_NEXT, func_cmd_play_next) }, {CMD_PLAY_PREV, func_cmd_play_prev) }, }; #define ARR_LEN(arr)sizeof(arr)/sizeof(arr[0]) void player_cmd_handle(int cmd, void* p) { for(int i = 0; i 《 ARR_LEN(player_cmd_func); i++) { if(player_cmd_func[i].cmd == cmd && NULL != player_cmd_func[i].func) { player_cmd_func[i].func(p); break; } } }

咦?好像代碼簡潔了不少哦,改完之后好有成就感。

身為追求卓越的程序員,我還是有點(diǎn)不滿意,可不可以不用for循環(huán),直接使用player_cmd_func[cmd].func(p);,這樣還可以免去查詢的步驟,提高效率?

想法是好的,如果上面的程序不用for循環(huán),有可能數(shù)組越界,還有如果有命令增加,順序下標(biāo)不對應(yīng)的問題。

之前,我在《C語言的奇技淫巧之五》中的第50條提到過這個(gè)方法,還立了個(gè)flag,我要用MACRO寫個(gè)更高效更好的代碼!

使用X-MACRO

你聽說過X-MACRO么?聽過沒聽過都沒關(guān)系,來,我們一起耍起來!

MACRO或者說宏定義(書上或者規(guī)范上一般講預(yù)處理)基本原因都很簡單,看看就很容易學(xué)會(huì)??雌饋砗孟褚彩瞧降瓱o奇,似乎沒什么大作用。但是,你可別小看它,我們將其安上個(gè)“X”就很牛逼(不知道這個(gè)是啥傳統(tǒng),對于某些函數(shù)的擴(kuò)展,喜歡在其前面或后面加個(gè)“X”,然后這個(gè)函數(shù)比之前的函數(shù)功能強(qiáng)大很多,Windows里面的Api就有這案例)。

X-MACRO是一種可靠維護(hù)代碼或數(shù)據(jù)的并行列表的技術(shù),其相應(yīng)項(xiàng)必須以相同的順序出現(xiàn)。它們在至少某些列表無法通過索引組成的地方(例如編譯時(shí))最有用。此類列表的示例尤其包括數(shù)組的初始化,枚舉常量和函數(shù)原型的聲明,語句序列和切換臂的生成等。X-MACRO的使用可以追溯到1960年代。它在現(xiàn)代C和C ++編程語言中仍然有用。

X-MACRO應(yīng)用程序包括兩部分:

列表元素的定義。

擴(kuò)展列表以生成聲明或語句的片段。

該列表由一個(gè)宏或頭文件(名為LIST)定義,該文件本身不生成任何代碼,而僅由一系列調(diào)用宏(通常稱為“ X”)與元素的數(shù)據(jù)組成。LIST的每個(gè)擴(kuò)展都在X定義之前加上一個(gè)list元素的語法。LIST的調(diào)用會(huì)為列表中的每個(gè)元素?cái)U(kuò)展X。

好了,少扯淡,我們是實(shí)戰(zhàn)派,搞點(diǎn)有用的東西。

對于MACRO有幾個(gè)明顯的特征:

MACRO實(shí)際上就是做替換工作;

宏定義的替換工作是在編譯前進(jìn)行的,即預(yù)編譯;

宏定義可以用undef取消,然后再重新反復(fù)定義。

我們就用這幾個(gè)特征把MACRO耍到牛X起來!

#define X(a,b)a int x = DEF_X(1,2); #undef DEF_X #define DEF_X(a,b)b int y = DEF_X(1,2);

從上面可以看到,這個(gè)x和y的值是不一樣的。

于是可以定義一個(gè)這樣的宏:

#define CMD_FUNC \ DEF_X(CMD_PLAY, func_cmd_play) \ DEF_X(CMD_PAUSE, func_cmd_pause) \ DEF_X(CMD_STOP, func_cmd_stop) \ DEF_X(CMD_PLAY_NEXT, func_cmd_play_next) \ DEF_X(CMD_PLAY_PREV, func_cmd_play_prev) \

CMD的enum可以這樣定義:

typedef enum { #define DEF_X(a,b) a, CMD_FUNC #undef DEF_X CMD_MAX }tCmd;

預(yù)編譯后,這實(shí)際上就是這樣的:

typedef enum { CMD_PLAY, CMD_PAUSE, CMD_STOP, CMD_PLAY_NEXT, CMD_PLAY_PREV, CMD_MAX }tCmd;

接著,我們按這種套路定義一個(gè)函數(shù)指針數(shù)組:

const pFunc player_funcs[] = { #define DEF_X(a,b) b, CMD_FUNC #undef DEF_X };

甚至,我們可以定義一個(gè)命令的字符串,以作打印信息用:

const char* str_cmd[] = { #define DEF_X(a,b) #a, CMD_FUNC #undef DEF_X };

只要這個(gè)DEF_X(a,b)里面的a和b是對應(yīng)關(guān)系正確的,CMD_FUNC后面的元素順序是所謂了,這個(gè)比前面的結(jié)構(gòu)體有天然優(yōu)勢。這樣,我們就可以直接用下標(biāo)開始操作了:

void player_cmd_handle(tCmd cmd, void* p) { if(cmd 《 CMD_MAX) { player_funcs[cmd](p); } else { printf(“Command(%d) invalid!\n”, cmd); } }

這不僅提高了效率,還不用擔(dān)心命令的順序問題。

這種X-MACRO的用法對分支結(jié)構(gòu),特別是消息命令的處理特別的方便高效。

以下附上該案例的完整測試源碼:

#include 《stdio.h》 #define FUNC_IN() printf(“enter %s \r\n”, __FUNCTION__) #define CMD_FUNC \ DEF_X(CMD_PLAY, func_cmd_play) \ DEF_X(CMD_PAUSE, func_cmd_pause) \ DEF_X(CMD_STOP, func_cmd_stop) \ DEF_X(CMD_PLAY_NEXT, func_cmd_play_next) \ DEF_X(CMD_PLAY_PREV, func_cmd_play_prev) \ typedef enum { #define DEF_X(a,b) a, CMD_FUNC #undef DEF_X CMD_MAX }tCmd; const char* str_cmd[] = { #define DEF_X(a,b) #a, CMD_FUNC #undef DEF_X }; typedef void(*pFunc)(void* p); void func_cmd_play(void* p) { FUNC_IN(); } void func_cmd_pause(void* p) { FUNC_IN(); } void func_cmd_stop(void* p) { FUNC_IN(); } void func_cmd_play_next(void* p) { FUNC_IN(); } void func_cmd_play_prev(void* p) { FUNC_IN(); } const pFunc player_funcs[] = { #define DEF_X(a,b) b, CMD_FUNC #undef DEF_X }; void player_cmd_handle(tCmd cmd, void* p) { if(cmd 《 CMD_MAX) { player_funcs[cmd](p); } else { printf(“Command(%d) invalid!\n”, cmd); } } int main(void) { player_cmd_handle(CMD_PAUSE, (void*)0); player_cmd_handle(100, (void*)0); return 0; }

留個(gè)作業(yè)題:

如何靈活地將一個(gè)結(jié)構(gòu)體的內(nèi)容系列化到一個(gè)數(shù)組中,以及如何將一個(gè)數(shù)組的內(nèi)容解系列化到結(jié)構(gòu)體中?

例如,將以下結(jié)構(gòu)體s的內(nèi)容copy到data中(別老想著memcopy哦):

typedef struct STRUCT_DATA { int a; char b; short c; }tStruct;tStruct s; unsigned char data[100];

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

    關(guān)注

    5059

    文章

    18973

    瀏覽量

    302038
  • 宏匯編器
    +關(guān)注

    關(guān)注

    0

    文章

    7

    瀏覽量

    8978
收藏 人收藏

    評論

    相關(guān)推薦

    SV中define定義的用法

    SV中使用預(yù)處理指令`define來定義,可以用來創(chuàng)建文本替換。根據(jù)場景不同,`define主要用來定義常量、簡化復(fù)雜的表達(dá)式或代碼段以及提高代碼的可移植性。其基本語法為:
    的頭像 發(fā)表于 10-21 14:22 ?196次閱讀

    鴻蒙Ability Kit(程序框架服務(wù))【UIAbility組件基本用法

    UIAbility組件的基本用法包括:指定UIAbility的啟動(dòng)頁面以及獲取UIAbility的上下文[UIAbilityContext]。
    的頭像 發(fā)表于 06-06 11:02 ?403次閱讀
    鴻蒙Ability Kit(程序框架服務(wù))【UIAbility組件基本<b class='flag-5'>用法</b>】

    鑫科技即將掛牌上市

    浙江鑫科技股份有限公司(以下簡稱“鑫科技”)成功獲得證監(jiān)會(huì)的批文,即將掛牌上市,這標(biāo)志著鑫科技將成為資本市場鍛造鋁合金車輪第一股,迎來產(chǎn)業(yè)經(jīng)營和資本運(yùn)營雙輪驅(qū)動(dòng)的全新發(fā)展階段。
    的頭像 發(fā)表于 03-11 15:30 ?727次閱讀

    AWTK 開源串口屏開發(fā)(10) - 告警信息的高級用法

    信息的顯示和管理。本文介紹告警信息的兩種高級用法:查詢告警信息。如果告警信息比較多,我們需要通過查詢,來快速找到我們想要的告警信息。查看告警信息的詳情。如果某條告警
    的頭像 發(fā)表于 02-24 08:23 ?282次閱讀
    AWTK 開源串口屏開發(fā)(10) - 告警信息的<b class='flag-5'>高級</b><b class='flag-5'>用法</b>

    法拉電容放電保護(hù)原理是什么?如何正常使用法拉電容?

    法拉電容放電保護(hù)原理是什么?如何正常使用法拉電容? 法拉電容放電保護(hù)原理是指通過合理的設(shè)計(jì)和配置,保護(hù)法拉電容在放電過程中不受損壞的一種技術(shù)手段。法拉電容是一種具有高能量密度和長壽命的電容器
    的頭像 發(fā)表于 02-02 11:34 ?1528次閱讀

    單片機(jī)定時(shí)器的用法

    本章以CW32通用定時(shí)器為例介紹單片機(jī)定時(shí)器的用法。
    的頭像 發(fā)表于 01-04 10:37 ?1310次閱讀
    單片機(jī)定時(shí)器的<b class='flag-5'>用法</b>

    split在python中的用法

    split在python中的用法 split()是Python中一個(gè)非常常用的字符串函數(shù),它能夠根據(jù)指定的分隔符將一個(gè)字符串分割成多個(gè)子字符串,并返回一個(gè)包含這些子字符串的列表。本文將詳細(xì)介紹
    的頭像 發(fā)表于 12-25 15:12 ?1944次閱讀

    Altium designer PCB高級規(guī)則—覆銅高級連接方式

    電子發(fā)燒友網(wǎng)站提供《Altium designer PCB高級規(guī)則—覆銅高級連接方式.pdf》資料免費(fèi)下載
    發(fā)表于 12-22 11:10 ?2次下載

    C語言-#和##的具體用法

    C語言中,在里面使用’#’和’##’有它非常神奇的作用。在定義的替換的過程中,#號可以作為一個(gè)預(yù)處理運(yùn)算符,把參數(shù)轉(zhuǎn)換為字符串。##運(yùn)算符則可以把兩個(gè)參數(shù)組合在一起。下面就來說
    的頭像 發(fā)表于 12-19 12:54 ?4720次閱讀
    C語言-#和##的具體<b class='flag-5'>用法</b>

    pcb插針的用法

    pcb插針的用法
    的頭像 發(fā)表于 12-13 16:58 ?1736次閱讀

    開關(guān)和按鈕的不同特點(diǎn)和用法!

    開關(guān)和按鈕的不同特點(diǎn)和用法! 開關(guān)和按鈕是常見的電子設(shè)備或電器的組成部分,它們在控制電路或操作電子設(shè)備時(shí)起著關(guān)鍵的作用。雖然它們都用于控制電氣電路的通斷或啟停,但它們之間存在一些不同的特點(diǎn)和用法
    的頭像 發(fā)表于 12-11 10:40 ?1195次閱讀

    plsy指令具體用法

    、WAV等。下面將詳細(xì)介紹plsy的用法。 安裝: 在使用plsy之前,首先需要在Unix系統(tǒng)中安裝。你可以通過在終端上運(yùn)行以下命令來安裝plsy: sudo apt-get install plsy
    的頭像 發(fā)表于 12-08 14:10 ?2142次閱讀

    如何規(guī)范和常量以及命名

    和常量 ◎ 定義和常量使用大寫字母或下劃線。 ◎ 用定義表達(dá)式時(shí),要使用完備的括號,如下: #define HEHE_AREA( a , b ) (( a ) * ( b )) ◎
    的頭像 發(fā)表于 12-07 14:49 ?668次閱讀

    oracle split函數(shù)用法舉例

    Split函數(shù)是Oracle中用于將一個(gè)字符串按照指定的分隔符進(jìn)行拆分的函數(shù)。其語法為:SPLIT(字符串, 分隔符)。 下面我將詳細(xì)介紹Split函數(shù)的用法,并提供一些示例,以幫助你更好地了解
    的頭像 發(fā)表于 12-06 09:56 ?8089次閱讀

    oracle中substr函數(shù)用法

    字符串的源字符串,start_position是子字符串的起始位置,length是要提取的子字符串的長度(可選參數(shù))。 下面將詳細(xì)介紹SUBSTR函數(shù)的用法及示例。 基本用法 最簡單的用法是只指定源
    的頭像 發(fā)表于 12-05 16:57 ?1185次閱讀