C/C++-技巧-巨集
一、巨集基礎
巨集在c/c++中扮演者比較重要的角色,雖然難以閱讀和除錯的缺點讓巨集的使用飽受詬病,但是在一些特殊的情況下,使用巨集會帶來極大的方便,甚至可以實現一些用其他方式無法實現的功能。
在c/c++程式編譯的過程中,編譯器對巨集的處理是在預編譯階段進行的,處理方式的核心思想是:簡單替換,編譯器並不會對巨集本身和巨集的引數進行任何型別、語法上的檢查,這也是導致巨集不易閱讀、不易除錯的原因,也可能產生一些比較隱蔽的陷阱破環程式原本設計的邏輯。
1、巨集的分類
巨集物件:沒有引數的巨集。這類巨集常常被用來定義常量,通常比較簡單,例如:
- #define MAX_NUM 100
- #define MAX(a, b) ((a)>(b) ? (a) : (b))
2、巨集的操作符
#:字串化一個巨集引數,即在引數名字前後加上"。例如:
- #define STRINGIZE(arg) #arg
#@:字元化一個巨集引數,即在引數名字前後加上'。例如:
- #define CHARIZE(arg) #@arg
- #define SYMBOL_CATENATE(arg1, arg2) arg1 ## arg2
注意:如果#、##操作的引數也是一個巨集,那麼這個巨集將不會被繼續展開,但是如果確實需要#、##後的巨集繼續展開,也可以定義輔助巨集過度一下:
- #define CHARIZE_WITH_MACRO(arg) CHARIZE(arg)
- #define SYMBOL_CATENATE_WITH_MACRO(arg1, arg2) SYMBOL_CATENATE(arg1, arg2)
\:換行,即開始新的一行繼續定義巨集體。例如:
- #define DEFINE_VARIABLE(name1, name2, type) type name1; \
- type name2;
3、變參巨集
巨集函式也可以接受個數不定的引數,形參寫為...,在巨集體內獲取形參使用__VA_ARGS__,例如:
- #define PRINTF(format, ...) printf(format, __VA_ARGS__);
- #define ATTR_1(arg) printf(arg);
- #define ATTR_2(arg, ...) ATTR_1(arg) ATTR_1(__VA_ARGS__)
- #define ATTR_3(arg, ...) ATTR_1(arg) ATTR_2(__VA_ARGS__)
- #define ATTR(args) args
- #define ATTR_1(arg) printf(arg);
- #define ATTR_2(arg, ...) ATTR_1(arg) ATTR(ATTR_1(__VA_ARGS__))
- #define ATTR_3(arg, ...) ATTR_1(arg) ATTR(ATTR_2(__VA_ARGS__))
4、內建巨集
c/c++標準中預定義了幾個巨集,只要編譯器是支援標準的即可以在程式碼中直接使用這些巨集:
__LINE__ // 當前程式碼行的行號
__FILE__ // 源程式的完整路徑
__DATE__ // 系統日期
__TIME__ // 系統時間
__TIMESTAMP__ // 系統時間戳
__FUNCTION__ // 當前程式碼行所在的函式的名字
__STDC__ // 當要求程式嚴格遵循ANSI C標準時該標識被賦值為1
__cplusplus // 當編寫C++程式時該識別符號被定義
另外有一些是編譯器相關的預定義巨集:
VC:_MSC_VER// VC編譯器版本號
更多參考:點選開啟連結
GCC/G++:__GNUC__// GNU編譯器版本號
二、常用巨集技巧
1、遍歷變參巨集的每個引數
巨集只是簡單替換的過程,所以不支援任何邏輯判斷語句,但是依然可以用多條巨集來實現相同的功能。
在實現遍歷遍歷每個巨集引數之前,先看看怎麼實現簡單的統計引數的個數。首先編譯器沒有提供任何可以直接使用來計算引數個數的方法,所以需要使用一點技巧來實現這個功能:數軸佔位,即把引數依次放到數軸每個點上,那麼最後一個沒被安放位置上的數就是引數的個數,不過這裡需要顛倒一下佔位,實現:
- // 假設巨集引數個數上限為10,否則需要手動擴充套件
- #define COUNT_PARMS_IMP(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, NUM, ...) NUM
- #define COUNT_PARMS(...) \
- ATTR(COUNT_PARMS_IMP(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
- // 假設巨集引數個數上限為10,否則需要手動擴充套件
- #define ARG_1(arg) printf(arg);
- #define ARG_2(arg, ...) ARG_1(arg) ATTR(ARG_1(__VA_ARGS__))
- #define ARG_3(arg, ...) ARG_1(arg) ATTR(ARG_2(__VA_ARGS__))
- #define ARG_4(arg, ...) ARG_1(arg) ATTR(ARG_3(__VA_ARGS__))
- #define ARG_5(arg, ...) ARG_1(arg) ATTR(ARG_4(__VA_ARGS__))
- #define ARG_6(arg, ...) ARG_1(arg) ATTR(ARG_5(__VA_ARGS__))
- #define ARG_7(arg, ...) ARG_1(arg) ATTR(ARG_6(__VA_ARGS__))
- #define ARG_8(arg, ...) ARG_1(arg) ATTR(ARG_7(__VA_ARGS__))
- #define ARG_9(arg, ...) ARG_1(arg) ATTR(ARG_8(__VA_ARGS__))
- #define ARG_10(arg, ...) ARG_1(arg) ATTR(ARG_9(__VA_ARGS__))
但是這樣的巨集有個缺點就是在使用時必須明確地指定呼叫有幾個引數的版本,不過有了前面實現的獲取引數個數的巨集,可以借用這個巨集來自動選擇哪個版本的引數遍歷巨集:
- #define ARGS(...) \
- ATTR(SYMBOL_CATENATE_WITH_MACRO(ARG_, ATTR(COUNT_PARMS(__VA_ARGS__)))(__VA_ARGS__))
2、跨平臺程式開發
一些編譯器提供的平臺相關的預定義巨集,可以很方便的用來做跨平臺開發,例如:
- #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
- // windows
- #elif defined(__linux__) || defined(__linux)
- // linux
- #endif
3、利用預定義巨集除錯程式
__FILE__、__LINE__、__FUNCTION__等可以很方便的獲取程式相關的資訊,當程式出現錯誤時,利用這些巨集可以及時地生成錯誤資訊並輸出到日誌中,以便檢視和除錯。
4、除錯巨集定義
巨集的缺點之一就是難以除錯,一旦巨集體的定義出現問題導致編譯錯誤,編譯器將報一些令人費解的錯誤。不過對於巨集定義導致的編譯錯誤,還是有一些方法除錯的:
(1)、檢視巨集展開後的完整程式碼
VC下可以利用"生成預處理檔案"選項,巨集展開後的程式碼將輸出到.i檔案中,操作參考:點選開啟連結
GCC下使用編譯選項-E即可。
5、巨集超程式設計
參考:點選開啟連結
相關文章
- c/c++巨集指令C++
- 理解C++ 巨集C++
- 《Effective C++》第1章 讓自己習慣C++-讀書筆記C++筆記
- 「Excel技巧」Excel技巧之如何看檔案裡的巨集?Excel
- C++-(25)-多執行緒-POSIX(3)-互斥量-pthread_mutexC++執行緒threadMutex
- c語言巨集的使用C語言
- C語言(巨集定義)C語言
- C++巨集定義#defineC++
- Objective-C 單例巨集Object單例
- c/c++標準預定義巨集C++
- 好用的一個object c 巨集Object
- C語言巨集中"#"和"##"的用法C語言
- iOS開發技巧:應用巨集定義使用字型iOS
- C語言——設計printf除錯巨集C語言除錯
- C++中巨集定義#define的用法C++
- C語言巨集和函式淺析C語言函式
- C語言巨集的高階應用C語言
- C語言-->(十四)結構體、巨集、編譯C語言結構體編譯
- 淺談C++物理設計:實用巨集C++
- 【C進階】21、巨集定義與使用分析
- Visual C++ MFC 中常用巨集的含義C++
- C++基礎(十二)一個巨集使用的坑C++
- C/C++語言巨集定義##連線符和符#的使用C++
- 有關C語言的知識---巨集定義用法C語言
- C語言中的標頭檔案中的巨集定義C語言
- C 語言巨集定義 #define 的理解與資料整理
- C語言巨集定義##連線符和#符的使用C語言
- C++語法小技巧C++
- 很酷的 C 語言技巧
- Rust 的巨集Rust
- [心得] CLisp巨集Lisp
- 巨集函式函式
- C語言巨集定義中#define中的井號#的使用C語言
- 教你看懂C++類庫函式定義之一---HRESULT 巨集C++函式
- iOS 通用巨集定義 高效全域性巨集彙總iOS
- C++ | VS2017 C++專案配置使用的常見巨集定義C++
- [C++]括號使用小技巧C++
- Oracle 20c 新特性:SQL 巨集支援(SQL Macro)Scalar 和 Table 模式OracleSQLMac模式