---
# System prepended metadata

title: C++：預處理器指示詞
tags: [Tutorial, Programming Language, C++]

---

{%hackmd BJrTq20hE %}
###### 作者：void
###### 修改時間：2021/11/09
# C++：預處理器指示詞

###### tags: `Programming Language` `C++` `Tutorial`

## 前言
> [color=#1f7c84]恩恩，最近閒來無事看了幾篇跟預處理器指示詞有關的寫法，我只能說令我震驚到了，原來 C++ 有這麼好用的東西我都不知道阿。從我第一次在高中碰到的時候，學到有關預處理器指示詞也只有 `#include` 跟 `#define` ，直到上研究所學長提起才知道預處理器指示詞不只這兩個，但當時~~懶惰~~忙碌沒有仔細看看這些指示詞，慚愧慚愧 ~ 。
> 
> 好了，廢話少說直接開看！

基本上預處理器指示詞是編譯器在看的，編譯器根據預處理器指示詞進行條件編譯。這個部分可以幫助我們在 Debug 、版本控管、處理相容性問題的時候，會有更靈活的運用。

## 概述
`#define`：定義一個巨集
`#undef`：取消定義一個巨集
`#if`：根據後方的條件句，決定是否編譯這段
`#ifdef`：如果後方的巨集名稱有被定義，則編譯以下程式碼
`#ifndef`：如果後方的巨集名稱沒有被定義，則編譯以下程式碼
`#elif`：之前的條件句判斷失敗，則繼續判斷此條件
`#else`：之前的條件句判斷失敗，則編譯以下程式
`#endif`：條件句的結尾

基本上這些不需要想的太難，其實也可以把它當成一般的程式讀，像是`#if`就如同 C 中的`if`，指示他們判斷的東西跟執行的動作不同而已，下面我們再繼續做介紹。

## 基本使用
> [color=#2db76e]我們高中資訊老師說：「想學個東西最好的方法是用他完成一個簡單的東西，你就有想學的動力了」

### #define
看看這段程式碼吧
```cpp=
#include <iostream>
#define TEST 1
using namespace std;

int main(){
    cout << TEST << endl;
}
```
對，就是這們簡單。基本上`#define`就很像定義一個變數一樣，我們稱它為識別碼。你說這個識別碼是啥，他就是啥，編譯的時候會把它直接把程式中出現識別碼`TEST`替換成`1`。所以你編譯的東西相當於：
```cpp=
#include <iostream>
using namespace std;

int main(){
    cout << 1 << endl;
}
```
但是並不是所有你`#define`過的識別碼都會被編譯器取代，這部分請參考 [Microsoft 的 docs](https://docs.microsoft.com/zh-tw/cpp/preprocessor/hash-define-directive-c-cpp?view=msvc-160)，不過今天寫的東西不會碰到這個部分，有興趣的可以自行鑽研。

### #undef
既然剛才有說定義`define`，那相反的就會有取消定義`undef`，基本上也十分好懂，就是把你定義過的識別碼取消，來看個例子。
```cpp=
#include <iostream>
#define TEST 1
#undef TEST //添加此行
using namespace std;

int main(){
    cout << TEST << endl;
}
```
你按下執行後，你可能會很疑惑為啥就跳出了`error: 'TEST' was not declared in this scope`，不過想想也很合理，畢竟是沒定義的`TEST`就只被當成一般的變數嘛 (摸頭燦笑，`#undef`非常簡單就只是把你定義過的識別碼刪掉。

## 條件編譯
### #if #elif #else #endif
下一個要討論的是條件編譯，這個部分我們更深入討論其他的預處理器指示詞。
讓我們來看個簡單的例子
```cpp=
#include <iostream>
#define TEST 1

using namespace std;

int main(){
    cout << TEST << endl;
    #if TEST == 1
    cout << "TEST = 1" << endl;
    #elif TEST == 2
    cout << "TEST = 2" << endl;
    #elif TEST == 3 || TEST == 4
    cout << "Maybe TEST = 3 or 4" << endl;
    #else
    cout << "TEST != 1, 2, 3, 4" << endl;
    #endif
}
```
`#if` `#elif` `#else`這幾個指示詞的邏輯就像是一般 C/C++ 中的`if` `else`一樣，識別碼進行條件判斷，判斷成功就編譯，失敗就跳過。`#endif`就沒甚麼好說的，基本上只要`#if`指示詞，後面就必須跟著`#endif`

> [color=#FF0000]:bulb: 不過要記住一點，指示詞是給編譯器看得，不是給程式看得，所以後面的條件句再進行判斷的時候，你只能使用`#define`過的識別碼，不能使用程式中的變數。



### #ifdef #ifndef
介紹這兩個指令之前先來看看以下程式碼
```cpp=
#include <iostream>
#define TEST

using namespace std;

int main(){
    cout << "Is TEST be defined ?" << endl;
    #if defined(TEST)
    cout << "TEST is defined" << endl;
    #endif
    
    #if !defined(TEST)
    cout << "TEST isn't defined" << endl;
    #endif
}
```
我們很常會出現像是上面的這種程式碼，用於檢測識別碼是否已經被定義了。也因為過於常用就被額外設定了兩個個預處理器指示詞`#ifdef` `#ifndef`。
所以上面的指示詞可以被寫成下面這樣：
```cpp=
#include <iostream>
#define TEST

using namespace std;

int main(){
    cout << "Is TEST be defined ?" << endl;
    #ifdef TEST
    cout << "TEST is defined" << endl;
    #endif
    
    #ifndef TEST
    cout << "TEST isn't defined" << endl;
    #endif
}
```
是不是簡潔多了呢？基本上也是可以搭配`#elif` `#else`一起使用，可以搞出很堆變化。

## 應用
大致上介紹完了幾個比較重要的指示詞後，後續介紹幾個比較常用的應用，看看能不能迸出什麼新滋味 ( ?

### Debug Mode
#### 情況
有的時候你程式碼寫的一長串，然後編譯過關是一回事，程式有沒有照自己想像的跑又是另一回事。這時候就會一直打`cout`或是`printf`，Debug完後，要留也不是；不留也不是，這時候預處理器指示詞可以提供一個比較乾淨的管理方式。我們來看看下面的程式碼：
```cpp=
//main.cpp
#include <iostream>
#define _A_DEBUG_ 0
#define _B_DEBUG_ 0

using namespace std;

int main(){
    int a = 0, b = 1;
    for(int i=0;i<=10;i++){
        a++;
        b *= 2;
        
        #if _A_DEBUG_ == 1
        cout << a << " ";
        #endif
        
        #if _B_DEBUG_ == 1
        cout << b << " ";
        #endif
    }
    cout << "a: " << a << endl;
    cout << "b: " << a << endl;
}
```
這個程式應該難不倒各位吧？如果說想看說`a` `b`在迴圈的情況，你可以透過調整最上面`#define`後識別碼的值開關是否要要進入 Debug 模式。

#### 改良
那既然他是給編譯器看的，那我們何不將它集中在一個 header file 中呢？
```cpp=
//config.h
#define _A_DEBUG_ 0
#define _B_DEBUG_ 0
```
```cpp=
//main
#include <iostream>
#include "config.h"

using namespace std;

int main(){
    int a = 0, b = 1;
    for(int i=0;i<=10;i++){
        a++;
        b *= 2;
        
        #if _A_DEBUG_ == 1
        cout << a << " ";
        #endif
        
        #if _B_DEBUG_ == 1
        cout << b << " ";
        #endif
    }
    cout << "a: " << a << endl;
    cout << "b: " << a << endl;
}
```
現在還只是小程式，當你有很多 Debug 陳述式的時候，`#define` 多到爆炸，那妳可以用這方法讓程式簡潔一點。
#### 再改良版
那如果你除了個別控制外，希望可以有個總開關的話也可以這麼寫：
```cpp=
#define _DEBUG_MODE_ 0
#define _A_DEBUG_ 0
#define _B_DEBUG_ 0

#if _DEBUG_MODE_ == 1
    #undef _A_DEBUG_
    #undef _B_DEBUG_
    #define _A_DEBUG_ 1
    #define _B_DEBUG_ 1
#endif
```
這麼寫可以覆蓋掉所有的設定，直接讓編譯器多編譯兩段 Debug 用的程式碼。也可以透過類似的方式控制你的 Debug 資訊想要顯示多少。

不過很可惜的就是並不存在重新定義的指示詞，所以我們只能先用`#undef`清掉之前的識別碼重新定義。

### 多檔案編譯
#### 情況
多檔案編譯的時候，每個不同的檔案都會匯入不同的 header ，但是很可能會這些 header 很可能會重複匯入，導致編譯器跳出`XXX is defined`的錯誤，我們用上面的程式小小修改一下：
```cpp=
//Foo.h
#include <vector>
class Foo{
    vector<int> x;
    Foo(){}
    int head(){
        if(!x.empty())
            return x[0];
        return -1;
    }
};
```
```cpp=
//Foo2.h
#include <vector>
class Foo2{
    vector<char> x;
    Foo2(){}
    char head(){
        if(!x.empty())
            return x[0];
        return '#'
    }
};
```
```cpp=
//main.cpp
#include <iostream>
#include <vector>
#include "Foo.h"
#include "Foo2.h"

using namespace std;

int main(){
    Foo test;
    Foo2 test2;
    vector<bool> test3;
}
```

#### 解決方法
上面的 `#include <vector>` 被重複匯入了，所以執行這個程式肯定是報錯的。但我們可以利用下面的技巧來迴避這個問題。
```cpp=
#ifndef _FOO_H_
#define _FOO_H_
... //程式碼
#endif
```
這個命名方式就是檔名全大寫，前後加 `_`，副檔名的 `.` 也換成 `_` 

#### 解決
上面的寫法代表如果這裡面有沒定義 (匯入) 的，則定義 (匯入)。所以要解決上面的問題很簡單：
```cpp=
//Foo.h
#ifndef _FOO_H_
#define _FOO_H_
#include <vector>
class Foo{
    vector<int> x;
    Foo(){}
    int head(){
        if(!x.empty())
            return x[0];
        return -1;
    }
};
#endif
```
```cpp=
//Foo2.h
#ifndef _FOO2_H_
#define _FOO2_H_
#include <vector>
class Foo2{
    vector<char> x;
    Foo2(){}
    char head(){
        if(!x.empty())
            return x[0];
        return '#'
    }
};
#endif
```
```cpp=
//main.cpp
#ifndef _MAIN_CPP_
#define _MAIN_CPP_
#include <iostream>
#include <vector>
#include "Foo.h"
#include "Foo2.h"

using namespace std;

int main(){
    Foo test;
    Foo2 test2;
    vector<bool> test3;
}
#endif
```
這樣就不會再發生重複定義或匯入的問題了。
> [color=#FF0000]:bulb: 隨手加上這三行程式碼是個好習慣，不管事你在做大專案，或是要程式交接，可以讓下一位接手你程式的人不會發生一堆相容性的問題。

## 本文網址
https://hackmd.io/@voidgeary/BkpVyzHvF

## 參考
Reference ➜ https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/81826/
Reference ➜ https://www.gushiciku.cn/pl/gL7M/zh-tw
Reference ➜ https://docs.microsoft.com/zh-tw/cpp/preprocessor/preprocessor?view=msvc-160