對于條件/分支處理的程序設計,我們慣性地會選擇switch-case或者if-else,這也是C語言老師當初教的。以下,我們用一個播放器的例子來說明,要實現的功能如下:
收到用戶操作播放器命令請求,如“播放”、“暫停”等,程序要對命令作區分;
針對不同的命令請求,作相應的處理;
輸出必要的輔助信息。
首先,將命令定義成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; }
實際上,這也沒什么毛病。但是,時間長了,需求不斷變更,程序不斷迭代,這個switch-case會變得非常冗長而很難維護。你不相信?我曾經見到過》1000行的類似這樣的代碼。如果讓你接手維護這樣的代碼,你內心會不會狂奔著萬千***?
但是,我不敢更改這個祖傳的switch-case啊,那么小心翼翼地將這些命令處理封裝成函數。像這樣:
#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; } }
后來,甲方還是不斷地更改需求,導致播放器的命令越來越多,幾十個上百個了……痛定思痛,我——要——改——革!!
解放switch-case/if-else
腦子里想來想去,度娘上翻來翻去,于是定義了個結構體:
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; } } }
咦?好像代碼簡潔了不少哦,改完之后好有成就感。
身為追求卓越的程序員,我還是有點不滿意,可不可以不用for循環,直接使用player_cmd_func[cmd].func(p);,這樣還可以免去查詢的步驟,提高效率?
想法是好的,如果上面的程序不用for循環,有可能數組越界,還有如果有命令增加,順序下標不對應的問題。
之前,我在《C語言的奇技淫巧之五》中的第50條提到過這個方法,還立了個flag,我要用MACRO寫個更高效更好的代碼!
使用X-MACRO
你聽說過X-MACRO么?聽過沒聽過都沒關系,來,我們一起耍起來!
MACRO或者說宏定義(書上或者規范上一般講預處理)基本原因都很簡單,看看就很容易學會。看起來好像也是平淡無奇,似乎沒什么大作用。但是,你可別小看它,我們將其安上個“X”就很牛逼(不知道這個是啥傳統,對于某些函數的擴展,喜歡在其前面或后面加個“X”,然后這個函數比之前的函數功能強大很多,Windows里面的Api就有這案例)。
X-MACRO是一種可靠維護代碼或數據的并行列表的技術,其相應項必須以相同的順序出現。它們在至少某些列表無法通過索引組成的地方(例如編譯時)最有用。此類列表的示例尤其包括數組的初始化,枚舉常量和函數原型的聲明,語句序列和切換臂的生成等。X-MACRO的使用可以追溯到1960年代。它在現代C和C ++編程語言中仍然有用。
X-MACRO應用程序包括兩部分:
列表元素的定義。
擴展列表以生成聲明或語句的片段。
該列表由一個宏或頭文件(名為LIST)定義,該文件本身不生成任何代碼,而僅由一系列調用宏(通常稱為“ X”)與元素的數據組成。LIST的每個擴展都在X定義之前加上一個list元素的語法。LIST的調用會為列表中的每個元素擴展X。
好了,少扯淡,我們是實戰派,搞點有用的東西。
對于MACRO有幾個明顯的特征:
MACRO實際上就是做替換工作;
宏定義的替換工作是在編譯前進行的,即預編譯;
宏定義可以用undef取消,然后再重新反復定義。
我們就用這幾個特征把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);
從上面可以看到,這個x和y的值是不一樣的。
于是可以定義一個這樣的宏:
#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;
預編譯后,這實際上就是這樣的:
typedef enum { CMD_PLAY, CMD_PAUSE, CMD_STOP, CMD_PLAY_NEXT, CMD_PLAY_PREV, CMD_MAX }tCmd;
接著,我們按這種套路定義一個函數指針數組:
const pFunc player_funcs[] = { #define DEF_X(a,b) b, CMD_FUNC #undef DEF_X };
甚至,我們可以定義一個命令的字符串,以作打印信息用:
const char* str_cmd[] = { #define DEF_X(a,b) #a, CMD_FUNC #undef DEF_X };
只要這個DEF_X(a,b)里面的a和b是對應關系正確的,CMD_FUNC后面的元素順序是所謂了,這個比前面的結構體有天然優勢。這樣,我們就可以直接用下標開始操作了:
void player_cmd_handle(tCmd cmd, void* p) { if(cmd 《 CMD_MAX) { player_funcs[cmd](p); } else { printf(“Command(%d) invalid!\n”, cmd); } }
這不僅提高了效率,還不用擔心命令的順序問題。
這種X-MACRO的用法對分支結構,特別是消息命令的處理特別的方便高效。
以下附上該案例的完整測試源碼:
#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; }
留個作業題:
如何靈活地將一個結構體的內容系列化到一個數組中,以及如何將一個數組的內容解系列化到結構體中?
例如,將以下結構體s的內容copy到data中(別老想著memcopy哦):
typedef struct STRUCT_DATA { int a; char b; short c; }tStruct;tStruct s; unsigned char data[100];
-
嵌入式
+關注
關注
5087文章
19145瀏覽量
306135 -
宏匯編器
+關注
關注
0文章
7瀏覽量
8995
發布評論請先 登錄
相關推薦
評論