// =======================================
// 定義 macro 常數 (#define )
#define PI 3.14159
#define e 2.71828
#define LENGTH 200
#define FILE_NAME (char*)("D:\\Demo.txt")
cout << "PI=" << PI << endl;
cout << "e=" << e << endl;
cout << "LENGTH=" << LENGTH << endl;
cout << "FILE_NAME:" << FILE_NAME << endl;
如果是要定義高度是長度的 5 倍,可以這麼定義
#define WIDTH 10
#define HEIGHT ((WIDTH)*5)
[Lemma]
以下感謝 Jacob Lee 指導
1. 若定義為常數時,可不用特別加上括號,即 #define PI (double)(3.14159)
直接用 #define PI 3.14159 即可 (除非那個 PI 是要做 float 使用);
若為運算式時,才需再加上括號。
2. 關於 PI,較好之方式為 const double PI = acos(-1); 若對於求 PI 有興趣的話,可參見吾人另一篇 求 PI (裡面筆者認為 Machin 公式 為最佳,但難免受限於浮點數誤差等問題) ,並不保證裡面所提之方法為最佳解法。
// =======================================
// 定義 macro 函式 (#define )
使用 #define 定義函式時掌握一個原則:括號永遠不嫌少!如果沒有適當的括號到時展開時會出問題。
#define MAX(a, b) ( (a) > (b) ? (a) : (b))
cout << MAX(1, 2) << endl;
Win32 中常用到的 HIWORD、LOWORD、HIBYTE、LOBYTE 也是由 macro 來的
#define LOWORD(a) ((WORD)(a))
#define HIWORD(a) ((WORD)(((DWORD)(a) >> 16) & 0xFFFF))
另有種寫法注意,
#define FOO (4+FOO)
上面這行 並不會 被一直展開成 4+(4+(4+....(4+FOO)+FOO)+FOO+....),
只會展開一層變 4 + FOO,參考 https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html#Self-Referential-Macros
相似的有如下
#define X (4+Y)
#define Y (2*X)
執行時 X 會被展開成 (4+(2*X));而 Y 會被展開成 (2*(4+Y)),要視程式碼裡面出現在是 X 還是 Y 。另外以下 macro 也被常用來定義
#define SWAP(a,b) do {a^=b; b^=a; a^=b;} while(0)
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))
#define GetRand(min, max) ((rand()%(int)(((max) + 1)-(min)))+ (min))
#define RADTODEG(x) ((x) * 57.29578)
// =======================================
// 避免多次引入表頭檔 (#ifndef)
同一個表頭檔為了多次引入問題,使用 #ifndef 方式解決
// EdisonX.h
#ifndef __EDISONX__
#define __EDISONX__
class EdisonX{
/* some script */
};
#define __EDISONX__
// =======================================
// 條件式編譯 (#if #elsif #endif)
這系列用到許多的 #if #elsif #else 以及 defined,如果要檢查電腦是什麼系統的話可以這麼做
#if defined(INTEL8080)
cout << "INTEL8080" << endl;
#elif defined(INTEL80x86)
cout << "MC680x0" << endl;
#elif defined(MC680x0)
cout << "MC680x0" << endl;
#else
cout << "OTHER" << endl;
#endif
程式設計到後來是常使用這使方式。例如要判斷是不是用 c++ compiler 編譯的話
#ifdefine _cplusplus
cout << "C++" << endl;
#else
cout << "C" << endl;
#endif
// =======================================
// 自訂錯誤訊息 (#error)
#error 是直接在將後面的述敘直接輸出在 IDE 視窗下,所以常用 #if 合用。
int i=0, j=0;
#if j==0
#error 分母為0
#endif
// =======================================
// Stringizing Operator (字串化, #)
#define 中加上 # 代表將該參數字串化,個人覺得用在路徑的表達會比較方便。
#include <iostream>
using namespace std;
#define Str(s) #s
int main()
{
cout << Str(EdisonX) << endl;
cout << Str("D:\EdisonX\Folder") << endl;
return 0;
}
EdisonX
"D:\EdisonX\Folder"
這個配合輸出指令(cout 、printf、puts)也很好用
#include <iostream>
using namespace std;
#define PRINT( s ) cout << #s << endl;
int main()
{
PRINT(EdisonX);
PRINT("D:\EdisonX\Folder");
return 0;
}
輸出結果與上面相同,不過注意的是,這種方式不能拿來輸出變數的內容,
string s = "EdisonX";
PRINT(s);
這樣輸出結果只會是 s 而已。
// =======================================
// Charizing Operator (字元化, #@)
這個很少看人用,將一個字元做字元化
#include <iostream>
#define CHR(x) (#@x)
int main()
{
char ch = CHR(a);
std::cout << ch << std::endl;
return 0;
}
結果將輸出字元 a
也可以這麼定義
#define CHR(X) #X[0]
// =======================================
// token-Pasting Operator (##)
這個中文不知道叫什麼,反正就是把它黏起來就是了。這裡的應用例子不少(也不知道是好是壞),先看一個例子就知道它在幹嘛的。
#include <iostream>
using namespace std;
#define VARCAT(x,y) x##y
int main()
{
int a0=0;
cout << VARCAT(a,0) << endl; // 等效 cout << a0 << endl;
VARCAT(co,ut) << 10 << endl; // 等效 cout << 10 << endl;
return 0;
}
第一個 VARCAT(a,0) 會合併成 a0 ,於是便等效於 cout << a0 << endl; 輸出即為 0;
第二個 VARCAT(co,ut) 會合併成 cout,於是便等效於 cout << 10 << endl; 輸出即為 10。
弄到這裡有些人有些想法了..
#include <iostream>
using namespace std;
#define VARCAT(x,y) x##y
int main()
{
int i, a0=0, a1=1, a2=2;
for(i=0; i<=2; i++) cout << VARCAT(a, i) << endl; // error
return 0;
}
很遺憾的,這段碼會死在 VARCAT(a, i) 這裡,因為它會視為 cout << ai << endl;
但變數裡面沒有 ai,所以不能這麼做。要做到這種地步的話,還是去弄個陣列比較方便。
這裡有個地方要注意
#include <iostream>
using namespace std;
#define CAT1(x, y) (x##y)
#define CAT2(x, y) CAT1(x, y)
#define n 0
int main()
{
int a0=0, a1=1, a2=2, an=-1;
cout << CAT1(a,n) << endl; // -1
cout << CAT2(a,n) << endl; // 0
return 0;
}
CAT1(a, n) ===> a##n ===> an
CAT2(a, n) ===> CAT1(a,0) ===> a##0 ===> a0
結果是不一樣的。
// =======================================
// Variadic Macros (不定參數之 marco, __VA_ARGS__)
這個 macro 很特別,可以說是 printf 的翻版,直接看以下範例。
#include <stdio.h>
#define PRINT(s, ...) printf( s, __VA_ARGS__)
int main()
{
int x=1;
double y = 1.23;
PRINT("x=%d,y=%.2lf\n", x, y);
return 0;
}
x=1,y=1.23
說穿了,也只是取代了 printf 裡面的引數功能而已。
// =======================================
// Predefined Macros (預設 macro)
標準常見的有下面幾項
_cplusplus:使用 c++ 編譯器
__FILE__ : 檔案名稱
__LINE__ : 行號
__DATE__ : 日期
__TIME__ : 時間
這五個較為常見,其中 __FILE__、__LINE__、__DATE__、__TIME__ 常在除錯時使用。另 VC 下又有自己額外的定義
_ATL_VER:ATL 版本
_CHAR_UNSIGNED:定義 char 為 unsigned 型態
_CLR_VER:定義 CLR 版本
_MSC_VER:VC 版本
_WIN32:Win32 及 Win64 應用程式時定義 (always defined)
以下是機台型號 (processor)
_M_IX86
_M_IA64
_M_IX86FP
_M_MPPC
_M_MRX000
.....
// =======================================
// C++ named operators
部份關係、邏輯運算子,C++ 裡面可用預設的 macro 去取代。
and |
&& |
and_eq |
&= |
bitand |
& |
bitor |
| |
compl |
~ |
not |
! |
not_eq |
!= |
or |
|| |
or_eq |
|= |
xor |
^ |
xor_eq |
^= |
// =======================================
// Others , 其它未盡事項
預處理器還有一些指令,有些是太簡單不想講 (#include);有些是不好用也不想講 (#line);更有些是太複雜不知道從何講起 (#pragma);總之,就先這樣吧,以後想到再做補充。