// =======================================
// 定義 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);總之,就先這樣吧,以後想到再做補充。

 

 

arrow
arrow
    全站熱搜

    edisonx 發表在 痞客邦 留言(1) 人氣()