---
# System prepended metadata

title: '[APCS] 程式語言及電腦科學基本觀念'
tags: [APCS]

---

# [APCS] 程式語言及電腦科學基本觀念


###### tags: `APCS`

APCS的觀念題使用的程式語言以C和C++為主，因此在這邊介紹的觀念也是以這兩種語言為基礎。

## 基本資料型態
一種不能以其他型態來定義的資料型態，稱為**純量資料型態**（scalar data type）。

幾乎所有的程式語言都會提供一組基本資料型態，由於資料型態各不相同，儲存所需的容量也不相同。

### 整數
C和C++中的**整數**（integer, `int`）和數學中的整數意義相同，儲存時會使用32位元（bit）的空間。

整數根據有無正負號之分，又可以分成以下兩種：
* 有號（signed）整數
    有號整數會保留32個位元中最左邊的位元來作為正負號的標記，0代表正數，1代表負數，因此能表示的最大值是 $2^{31} - 1$，最小值是 $-2^{31}$。
* 無號（unsigned）整數
    無號整數把32個位元全部都用來表示數值，因此只能表示正整數和0，並且最大值是 $2^{32} - 1$。

根據佔有的空間（也就是可以表示的值的範圍）不同，還有short（短整數）、long（長整數）等類型。

當所賦予的值超過了資料型態能接受的範圍，就會發生**溢位**（overflow）的情形，C的處理方式可以看成是一種「循環」的概念，可以參考[這篇文章](https://www.796t.com/content/1548347428.html)。

### 浮點數
在C和C++中，**浮點數**（floating point, `float`）可以根據所佔有的空間區分為**單精度浮點數**（float）和**雙精度浮點數**(double)。單精度浮點數使用32位元儲存數值，雙精度則使用了64位元。

### 字元
字元（character, `char`）型態包含了大小寫字母、數字、標點符號和控制符號（換行、空白等），在記憶體中用整數的數值來儲存，每一個字元佔用了8個位元。字元ASCII編碼的數值位於0到127之間，但最左邊的位元是用來核對用，因此實際上使用到的位元只有七個，所以最多只能表示 $2^7 = 128$ 個不同的字元。

在C和C++中要表示一個字元，需要用單引號把字元包起來，或者直接使用ASCII碼來表示：
```cpp=
char a = 'a';  // 小寫 a
char b = 65;   // 大寫 A
```
### 布林值
C++比C語言多了一種**布林值**（boolean, `bool`）型態，包含`true`和`false`。


## 數字系統
人類慣用的數字概念，是使用10進位來計算，也就是用1到9這些數字作為計量的符號，並每逢10就進一位。但在電腦中卻是使用2進位系統為主，這是因為如此會比較容易構建出電路的計算機。把0和1想成是一個開關的兩個狀態，這樣就會非常容易實作。更深度的討論，可以參考[這篇文章](https://kopu.chat/%e4%b8%96%e7%95%8c%e4%b8%8a%e5%8f%aa%e6%9c%8910%e7%a8%ae%e4%ba%ba%ef%bc%8c%e4%b8%80%e7%a8%ae%e6%98%af%e6%87%82%e4%ba%8c%e9%80%b2%e4%bd%8d%e7%9a%84/)。

由2進位延伸出來的8進位和16進位也是常見的表示方式。因為2進位表示的數字對於人類來說過於冗長，採用8進位和16進位可以簡潔許多。

* 2進位
**2進位**（binary, bin）指以2為底數的記數系統，只需要用兩個不同的數字0和1來表示，每個數字稱為一個**位元**（bit, binary digit 的縮寫）。
在C、C++、Python、Java等語言中，使用字首`0b`來表示2進位的數值，例如`0b1001`。
* 16進位
**16進位**（hex）在數學中是一種逢16進1的進位制。一般用數位0到9和字母A到F表示，其中A-F相當於十進位的10-15。
在C、C++、Python、Java等語言中，使用字首`0x`來表示16進位的數值，例如`0x5A3`。
* 8進位
**8進位**（oct）是以8為底的進位制，使用數字0到7。
從2進位的數轉換到8進位的數，可以將3個連續的數字拼成1組，再獨立轉成8進位的數字。例如10進位的`74`，即2進位的`1001010`，3個1組變成`1 001 010`，再變成8進位中的`112`。
8進位（相較於16進位）的優點主要是它不必用數字以外的符號（十六進位除了0-9之外，要用到A-F）等。
在C和C++中，表示8進位的數字時只要在前面加上`0`即可，例如`065`；Python則是要加上`0o`，例如`0o65`。

### 數字系統的轉換
* 非10進位轉換成10進位
我們先理解一下10進位制的原理。以 $1234.56$ 為例，我們可以把它分解成：
$$
1234.56_{10} = 1 \times 10^{3} + 2 \times 10 ^2 + 3 \times 10 ^1 + 4 \times 10 ^0 + 5 \times 10 ^{-1} + 6 \times 10 ^{-2}
$$注意觀察係數和底數（這裡就是 $10$）的次方數。
如果底數是8的情況呢？
$$
23.54_8 = 2 \times 8^1 + 3 \times 8^0 + 5 \times 8^{-1} + 4 \times 8^{-2} = 19.6875_{10}
$$16進位也是一樣的道理:
$$
\text{FC3.92B}_{16} = 15 \times 16^2 + 12 \times 16^1 + 3 \times 16^0 + 9 \times 16^{-1} + 2 \times 16^{-2} + 11 \times 16^{-3} = 3843.5766015625_{10}
$$
* 10進位轉換成非10進位
從10進位轉換成非10進位，需要用到**短除法**和**長除法**這兩個工具。以2進位為例，在這邊我們想把 $53.48_{10}$ 轉換成2進位。我們必須把這個數分為整數部分和小數部分，短除法用於整數部分，長除法用於小數部分：
![](https://hackmd.io/_uploads/Hk3aPXG2j.png)
8進位和16進位也是一樣的道理，在這邊就不贅述了。

### 例題
* 如果 $X_n$ 代表 $X$ 這個數字是 $n$ 進位，則 $\text{D02A}_{16} + 5487_{10}$ 是多少？
(A) $1100~0101~1001~1001 _2$
(B) $162631 _{8}$
\(C\) $58787_{16}$
(D) $\text{F599}_{16}$
    :::spoiler 解答
    (B)
    一般我們習慣做的加減法是在10進位上進行，但這題的選項全部都是由2進位延伸出來的，因此轉換到相關的進位會比較容易計算。
    :::

* 以下何者最大？
(A) $(101001 - 10010)_2$
(B) $(66 - 57)_{8}$
\(C\) $(102 - 94)_{16}$
(D) $(\text{3C} - 34)_{16}$
    :::spoiler 解答
    \(C\)
    :::
    
    
## 運算子
運算式就像數學中的算式一樣，由**運算子**（operator）和**運算元**（operand）所組成。在程式語言中，運算子多以符號表示，通常都無法再化約成更小的單位，所以運算子可視為該語言的基礎指令。

### 指派運算子
`=`在數學中代表的是等於的意思；在程式語言中，主要用於將`=`**右邊的值指派給左邊的變數**，這個運算式由至少兩個運算元（左邊的變數和右邊的值）構成。例如：
```cpp=
int a = 3;
a = a + 1;
```
其中，`a = a + 1`是一個很經典的運算式，因為這在數學上不可能成立，但在程式語言中，會先估算出右邊的值，再把估算出來的值指派給左邊的變數。這個運算式也可以寫成`a += 1`，或者`a++`（不過Python並不支援`a++`這種寫法）。其中`+=`屬於複合指派運算子，其他同樣屬於複合指派運算子的還有`-=`、`*=`、`/=`等，使用的邏輯都和`+=`相同：
```cpp=
a -= 1;   // 相當於 a = a - 1;
a *= 12;  // 相當於 a = a * 12;
a /= 6;   // 相當於 a = a / 6;
```
### 算術運算子
算術運算子是最常用的運算子類型，包含了數學中的四則運算，以及遞增、遞減、正負號等：
![](https://hackmd.io/_uploads/HJftx8Ghj.png)

### 關係運算子
關係運算子主要用來比較兩個運算元的大小關係。當使用關係運算子時，運算的結果會有成立（true）和不成立（false）兩種。但C語言並沒有用來表示true和false的布林值型態，要怎麼回傳關係運算式的結果呢？答案是使用數值0和1，0對應的是false，1對應的是true。
![](https://hackmd.io/_uploads/BJCRmIGhj.png)

### 邏輯運算子
邏輯運算子經常在以判斷式進行流程控制時使用，用來判斷兩個運算式之間的關係，其輸出結果和比較運算式一樣都是true或false。

| 運算子 | 功能 | 範例 |
| -------- | -------- | -------- |
| `&&`     | AND     | `a >= b && a <= c`     |
|`\|\|`|OR|`a > b \|\| a < c`|
|`!`|NOT|`!(a > b)`|

邏輯運算子也可以連續使用，例如：
```cpp=
(a < b) && (b < c) || (c < a)
```
這條運算式會從左至右被估算，也就是先計算`(a < b) && (b < c)`的結果，再把這個結果和`(c < a)`做OR的計算。

### 位元運算子
位元運算子可以被用來處理整數和字元資料裡面的位元，主要分為**位元邏輯運算子**和**位元位移運算子兩種**。

位元運算對初學者來說的確較不常用，但如果用的洽當的話，可以增進不少程式效率

* 位元邏輯運算子
位元邏輯運算子和前面提到的邏輯運算子並不相同。邏輯運算子是對數值整體作判斷，而位元邏輯運算子是將數值中的每個位元一一去做運算。
    - `&` (AND)：兩個位元都是1時，估算結果才是1。
    - `|` (OR)：兩個位元只要有任一個是1時，估算結果就是1。
    - `^` (XOR)：兩個位元只要有任一個是1時，估算結果就是1；但若兩個位元相同時，估算結果就是0。
    - `~` (NOT)：把位元值對調，0變成1，1變成0。

* 位元位移運算子
位元位移運算子可將位元向左或向右移動指定的位元數，包括左移（`<<`）和右移（`>>`）運算子。
    - `<<`（左移）：`a << n`可以把`a`這個變數向左移動`n`個位元，超出儲存範圍的位元捨去，右邊多出來的位元補0。
    ![](https://hackmd.io/_uploads/ry42ASQhj.png)

    - `>>`（右移）：`a >> n`可以把`a`這個變數向右移動`n`個位元，也就是把最右邊的`n`個位元捨去。左邊多出來的部分，原本的數若是正數則補上0，負數則補上1。
    ![](https://hackmd.io/_uploads/HywcAHm2i.png)
### 運算子優先順序
![](https://hackmd.io/_uploads/r106ckpns.png)

### 2補數
**2補數**（2's complement）是一種用2進位表示有符號數的方法，也是一種將數字的正負號變號的方式。也就是說，要知道一個負數在電腦中是怎麼用2進位表示的話，必須先計算它的**相反數的2進位表示法**，再求取它的**2補數**。

現在說明如何求取2補數。求取一個數`x`的2補數的步驟如下：
1. 取`x`的1補數，也就是`~x`(NOT `x`)
2. 再加上1，即為`x`的2補數

以`-12`為例，我們知道`12`的2進位表示是（為簡便以8位元表示）：
```
0000 1100
```
接著我們需要求1補數，也就是把所有的位元反轉：
```
1111 0011
```
再加上1，得到
```
1111 0100
```
這就是`-12`在電腦中的2進位表示法。

### 例題
* `x`、`y`、`z`均為布林變數，且`x = true`、`y = true`、`z = false`。求：
    - `!(y || z) || x`
    - `!y || (z || x)`
    - `z || (x && (y || z))`
    - `(x || x) && z`

    :::spoiler 解答
    `true / true / true / false`
    :::
    
    
* 若 `!(x || y)` 為true，則x和y分別為？
    :::spoiler 解答
    兩個都只能是`false`。
    :::
    
* 求 $(10110110)_2$ 的1補數和2補數。
    :::spoiler 解答
    1補數：$01001001$
    2補數：$01001010$
    :::

* 若`!a && !b && !c`為`true`，且`a`為`false`，則`b`和`c`分別為？
    :::spoiler 解答
    兩個都是`false`。
    :::
    
## 資料型態轉換
在C和C++中，型態轉換分為由編譯器自動執行的**自動轉換**（或稱**隱式轉換**）及編寫程式者用轉型運算子執行的**強制轉換**。

### 自動轉換
自動轉換有兩種：
* **提升**（promotion）：將較小的資料型態轉為較大的資料型態（**小轉大**）。進行運算時，若運算元型態不同，則要將型態都提升和較大運算元相同才可進行運算。
* **截斷**：將較大的資料型態轉為較小的資料型態，會捨棄部份內容（**大轉小**）。若將型態較大的值指派給型態較小的變數，則編譯器會自動進行截斷。

自動轉換的時機如下：
* 不同型態的指派
    例如，將浮點數指派給整數，小數部份會被強制去除：
    ```cpp=
    float a = 5.7;
    int b = a;  // 結果 b = 5
    ```
* 多種型態的運算
    例如把整數加到浮點數上時，整數會被轉換成浮點數：
    ```cpp=
    float a = 5.3;
    int b = 3;
    a = a + b;  // 結果 a = 8.3
    ```
* 傳遞和函式宣告的型態不同的引數給函式時
    當函式宣告為整數，傳遞浮點數給函式時，浮點數會被轉換成整數：
    ```cpp=
    float a = 5.3; 
    func1(a);

    void func1(int num)  { }
    ```

### lvalue 和 rvalue
在這裡要提一下lvalue（左值） 和 rvalue（右值）的概念。在型態轉換時，我們會以等號左邊的值，也就是 lvalue 的型態為主，lvalue 和 rvalue 的主要差異在哪裡呢？

每個運算式當中都會有所謂的 lvalue 跟 rvalue。lvalue 通常指的是運算式後還保留其狀態的一個物件，通常所有的變數都是 lvalue。rvalue 通常是一個運算式，在那個運算式以後，其狀態就不會被保留了，也就是一個暫時存在的數值。具體來說：

* lvalue 是保存在單一運算式之外的物件。可以將左值當做具有名稱的物件。所有變數都是左值，包括不可修改的 (const) 變數。
* rvalue 是暫存值，不會在使用它的運算式之外保存。

所以，在進行自動型態轉換時，會將 rvalue 的型態轉換以配合 lvalue 宣告的型態。


### 強制轉換
在 C 和 C++ 中，我們也可以針對運算式執行上的要求，使用型態轉換運算子（cast operator）「暫時性」地轉換資料的型態。注意，在這邊資料型態的轉換只是針對變數所儲存的值去做變換，並不會影響變數本身的資料型態。舉例來說：
```cpp=
int i = 100, j = 3;
float result;
result = i / j;
```
運算式會把`i / j`的結果，也就是33（不包含小數點後的部分）轉換成`float`再指定給`result`，也就是說，最後result的值會是33.000000，這不是我們希望得到的。所以必須改成：
```cpp=
result = (float) i / (float) j;
```
這麼一來就能得到想要的結果。

另外，lvalue也不能做這樣的變換。也就是說，下面這樣的運算式是不合法的：
```cpp=
(float) avg = (i + j) / 2;
```

關於型態轉換的一些小規則，可以參考[這篇文章](http://wen00072.github.io/blog/2015/05/08/conversion-in-c/)。


## 變數與常數
**變數**（**一般變數**）和**常數**（**常數變數**）是用來儲存資料以供運算使用的工具。C和C++在宣告變數時，必須連變數對應的資料型態一起宣告，如此才能在記憶體中保留一個固定大小的區塊給變數使用。

常數變數和一般變數最大的差別是常數變數的值一經宣告之後就無法再被改變，也就是說，常數變數是「唯讀（read only）」的。

### 變數的命名
於程式設計時我們必須為每一個變數、常數及函式命名，以上所有變數、常數及函式等名稱，統稱為程式語言的**識別字**（identifier）。識別字命名規則如下:

* 必須以英文字母或下底線開頭，不能以數字作為開頭。
* 只能有英文字母、下劃線`_`、美元符號`$` 及數字，不得包含空白。
* 不能使用 C/C++ 的**保留字**（keywords，又稱**關鍵字**）作為變數名稱 (例如：new、float 等)。
![](https://hackmd.io/_uploads/HyvSHyR3s.png)
* 識別字的長度不可以超過 32 個字元。
* 識別字的大小寫均視為不同。

一般會建議變數名稱要盡量取得有意義，否則變數變多時程式碼會變得難以閱讀。一些在各種語言中可以遵循的命名慣例（並非硬性規定）可以參考[這篇文章](https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E8%AE%8A%E6%95%B8%E5%91%BD%E5%90%8D-f53cd1115076)。

### 變數宣告與初始化

若在程式中需要使用到一個變數，那我們必須在使用之前先宣告這個變數。
宣告的格式為`資料型態 變數名稱;`
假設我們要宣告一個整數變數，名稱為a，則看起來會像：
```cpp=
int a;
```
我們也可以在宣告變數的同時給他一個初始值，這個行為稱為初始化(initialize)。假設這次我們想要讓a的初始值為10，則有以下兩種寫法:
```cpp=
int a = 10;
int a(10);
```
通常以第一種較為常見，也比較容易閱讀。

若沒有手動給予初始值，根據不同的編譯器會給予不同的初值（未定義行為，undefined behavior），所以請不要在沒有給任何值的情況下使用變數。

除了可以給予10進位初始值外，當然也可以給予16進位初始值，寫法如下:
```cpp=
int a = 0x66aaff;
int a(0x66aaff);
```

同時宣告多個同型態的變數時，可以使用逗號`,`分隔：
```cpp=
int a, b, c;
```

### 常數變數
關鍵字`const`用來定義常數（constant），以防止一些不希望變更的值被意外修改。凡是以`const`宣告後的變數設定初值後，都不能重新指派新的值。下面宣告`PI`為常數並且設定初值為 3.14：
```cpp=
const float PI = 3.14;
```
如果我想重新設定`PI`的值為 3，會無法通過編譯。

常數變數的名稱通常會使用全大寫的英文字母，並以底線分隔單字。

## 輸入和輸出（I/O）
### C
在C裡面，標準輸入輸出是由`stdio.h`提供，因此若要使用輸入輸出的功能，必須在程式的最開始加上：
```c=
#include <stdio.h>
```
* 標準輸出：`printf()`
    將訊息輸出至設備，稱之為標準輸出（standard output），C 藉由`printf()`將訊息輸出。基本上，`printf()`就是將指定的文字、數值等輸出至螢幕上，並且執行過後會傳回所輸出的字元數。`printf()`的使用方法如下：
    ```cpp=
    printf(格式化字串, 引數列);
    ```
    在`printf()`裡面的引數列，可以是變數、常數甚至是運算式的組合。引數列中的每一個項目，必須對應到格式化字串中以`%`開頭的**格式化字元**，才能夠正常輸出。一些常用的格式指定字元如下：
    * `%c`：以字元方式輸出
    * `%d`：10 進位整數輸出
    * `%o`：以 8 進位整數方式輸出
    * `%u`：無號整數輸出
    * `%x`、`%X`：將整數以 16 進位方式輸出
    * `%f`：浮點數輸出
    * `%e`、`%E`：使用科學記號顯示浮點數
    * `%g`、`%G`：浮點數輸出，取 %f 或 %e（%f 或 %E），看哪個表示精簡
    * `%s`：字串輸出
    * `%lu`：long unsigned 型態的整數
    * `%p`：指標型態
    * `%%`：顯示 %
    
    你可以在輸出浮點數時指定精度，使用語法為在`%f`的中間加入小數點後想留下的位數：
    ```cpp=
    printf("a=%.2f", 19.2345);
    ```
    其輸出結果會是
    ```=
    a=19.23
    ```
    也可以指定輸出時，至少要預留的字元寬度，無論是數值或字串，例如：
    ```cpp=
    printf("a=%7.2f\n", 19.2345);
    ```
    其中整數部分7表示預留7個字元寬度，不足的部份要由空白字元補上。由於19.23只佔5個字元，所以補上2個空白在前端，其輸出結果會是
    ```=
    a=  19.23
    ```
    若在`%`之後加上負號，例如`%-6.2f`，表示靠左對齊，沒有加則是靠右對齊。
    若事先無法決定字元寬度，則可以使用`*`，例如：
    ```cpp=
    printf("%*d", 3, 1);
    printf("%3d", 1);
    ```
    這兩行程式的輸出會是一樣的。
* 標準輸入：`scanf()`    
    如果打算取得使用者的輸入，可以使用`scanf()`函式，並搭配格式化字元與`&`（取址運算子）指定給變數，例如：
    ```cpp=
    #include <stdio.h>

    int main(void) {
        int input;

        printf("請輸入數字：");
        scanf("%d", &input);

        printf("你輸入的數字：%d\n", input);

        return 0;
    }
    ```
    在程式中先宣告了一個整數變數`input`。使用`scanf()`函式時，必須使用對應的格式指定字，例如這邊輸入的數值為整數，使用的格式化字元就是`%d`。
    你還必須告知`scanf()`函式儲存資料的變數位址，為此，必須使用`&`取址運算子，這會給出變數的記憶體位址。特別的是，但字元陣列名稱本身就有位址資訊，所以不必使用`&`來取址。
    `scanf()`在接受輸入時，可以接受多個值，也可以指定輸入的格式，例如：
    ```cpp=
    #include <stdio.h>

    int main(void) {
        int number1, number2;

        printf("請輸入兩個數字，中間使用空白區隔）：");
        scanf("%d %d", &number1, &number2);
        printf("你輸入的數字：%d %d\n", number1, number2);

        printf("請再輸入兩個數字，中間使用-號區隔）：");
        scanf("%d-%d", &number1, &number2);
        printf("你輸入的數字：%d-%d\n", number1, number2);

        return 0;
    }
    ```    
    在第一個`scanf()`中，指定了使用空白來區隔兩個輸入，而第二個`scanf()`中，指定了使用`-`來區隔兩個輸入。執行與輸入的結果如下所示：
    ```=
    請輸入兩個數字，中間使用空白區隔）：10 20
    你輸入的數字：10 20
    請再輸入兩個數字，中間使用-號區隔）：30-40
    你輸入的數字：30-40
    ```

### C++
在C++裡面，輸入和輸出可以直接利用I/O運算子來進行，且不需要再搭配格式化字元。在使用這些工具之前，需要引用`iostream`標頭檔：
```cpp=
#include <iostream>
```
C\++中的I/O操作使用**流**（stream）的概念。C\++中把資料之間的傳輸操作稱為流，流既可以表示資料從記憶體傳送到某個設備中，稱為**輸出流**（ostream），也可以表示數據從某個設備傳送到記憶體的變數中，稱為**輸入流**（istream）。
![](https://hackmd.io/_uploads/rki3vbCpj.png)
C\++定義了兩個分別用來進行資料流輸出和輸入的物件：`cout`和`cin`。
* 標準輸出流：`cout`
    `cout`與標準輸出設備連接，通常是一個螢幕，與流插入運算子(`<<`)結合使用（表示資料從記憶體插入到輸出流中），可以顯示輸出：
    ```cpp=
    #include <iostream>  
    using namespace std;  
    int main() {  
        cout << "Hello World" << endl; 
        return 0;
    }
    ```
    注意這邊必須使用`using namespace std`來引用命名空間`std`，否則`cout`必須寫成`std::cout`。`endl`是用來做跳行控制的，但要注意不當使用會造成程式效率變差。
* 標準輸入流：`cin`
    `cin`標準輸入設備連接，標準輸入設備通常是鍵盤。與流提取運算子(`>>`)結合使用（表示資料從輸入流提取到記憶體的變數中），可以讀取輸入：
    ```cpp=
    #include <iostream>  
    using namespace std;  
    int main() {  
        int age;  
        cout << "Enter your age: ";  
        cin >> age;  
        cout << "Your age is: " << age << endl;
        return 0;
    }
    ```
    執行上面的程式可以得到以下結果：
    ```=
    Enter your age: 12
    Your age is: 12
    ```
    
## 前置處理器和巨集
**前置處理器**（preprocessor）是在 C 或 C++ 中所使用的**巨集**（macro）語言。嚴格說來，前置處理器的語法不是 C 語言，而是一個和 C 語言共生的小型語言。

在編譯器中，前置處理器和實質的編譯器是分開的。程式碼會經過前置處理器預處理過後，再轉給真正的編譯器，進行編譯的動作。

預處理在本質上是一種字串代換的過程。前置處理器會將程式碼中巨集宣告的部分，代換成不含巨集的程式碼。之後再將處理過的程式碼轉給編譯器，進行真正的編譯。
### `#include`
`#include`可以用來引入外部檔案，除了C和C++提供的標頭檔以外，也可以引入自己寫的檔案，讓它們成為目前程式碼的一部分，語法如下：
```cpp=
#include <檔案名稱>
#include "檔案名稱"
```
如果在`#include`後使用的是角括號`<>`，則前置處理器將至預設的系統資料夾中尋找指定的檔案；如果使用的是雙引號，則前置處理器會先在當前工作的資料夾尋找引用的檔案，如果找不到，才會去預設的系統資料夾尋找。因此，若要引用預設的標準函式庫，雖然也可以用`#include "stdio.h"`，但效率上會比較差。

### `#define`
#define 敘述用來宣告巨集。這應該是前置處理器中最具可玩性的部分。有些人會用巨集寫擬函式，甚至會用巨集創造語法。語法如下：
```cpp=
#define 巨集名稱 表示式  //不需要加;
```
最簡單的巨集是宣告定值：
```cpp=
#define SIZE 10
```
在轉換後的程式中，並沒有`SIZE`這個變數。每個`SIZE`所在的位置會被前置處理器代換為10。

稍微進階一點的用法是用巨集寫簡單的擬函式。像是以下的 `MAX`巨集：
```cpp=
#define MAX(a, b) ((a) > (b) ? (a) : (b))
```
實際上，該巨集會代換為三元運算子，所以可以像函式般回傳值。

巨集更複雜的用例可以參考[這篇文章](https://opensourcedoc.com/c-programming/preprocessor/)，在這邊就不多做說明了。


### 例題
* 以下程式碼輸出結果為何？
    ```cpp=
    int a = 2, b = 3;
    int c = 4, d = 5;
    int val;
    val = b / a + c / b + d / b;
    printf("%d\n", val);
    ```
    :::spoiler 解答
    3
    :::

* 請問以下程式會輸出什麼？
    ```cpp=
    #define boo(a, b) (a + b)

    int main(){
        cout << boo(3, 5) * boo(3, 5) << endl;
    }
    ```
    :::spoiler 解答
    64
    :::