---
# System prepended metadata

title: 7.C Pointers
tags: [學校門口大樹石頭下, 程設好難, 他的課就很好睡阿]

---

# 7.C Pointers

## 7.1 Introduction
- 指標
一種**儲存位址**的特殊變數，使程式能夠==傳址（**pass-by-reference**）==，在函數之間傳遞函數，並創建和操作動態數據結構，也就是說可以增長和收縮的數據結構執行時間。
## 7.2 Pointer Variable Definitions and Initialization
### 7.2.1 Declaring Pointers
- 指標是其值為**內存地址**（**memory address**）的變數
- 一個變數通常直接包含一個特定的值。然而，一個指標包含一個變數的地址，該地址包含一個特定的價值。換句話說，通過指標引用值稱為**間接引用**（**indirection**）。
![](https://i.imgur.com/sRI6igy.png)
- 宣告指標與所有變數一樣，==指標必須在**使用前**進行定義==
```c=
int *countPtr, count;
```
上述程式碼的翻譯：可以儲存 int 型態變數的位址的指標為 countPtr，抑或是稱為**指向** int 型態的指標 countPtr
>用於宣告指標變數的星號（**asterisk**） * 表示法並未分配給所有變數宣告中的名稱。因此，每個**指標都必須**以 * 為前綴來宣告姓名!
> [name=小蜜蜂]
```c=
int *xPtr, *yPtr;
```
>我們更喜歡在指標變數名中**包含字母** Ptr 以**表明**這些變數是**指標**，需要適當處理
> [name=小蜜蜂]
### 7.2.2 Initializing and Assigning Values to Pointers
- 為指標==初始化（**Initializing**）和賦予值（**Assigning values**）==
- 指標應該**在定義時初始化**，或者可以為它們分配一個值。 一種指標可以初始化為 NULL、0 或地址。 值為 NULL 的指標**指向一無所有**。 NULL 是在 ＜stddef.h> 頭文件中定義的符號常數（和其他幾個標頭，例如 <stdio.h>）。 
- 將指標初始化為 0 等價於**初始化指標**到**空指標**（**NULL**），但空指標（NULL）是首選，因為它強調變數是指標類型的事實。當被指派為 0 時，首先將其轉換為適當類型的指標。值為 0 是**唯一可以直接賦予**指標變數的整數值。
>初始化指標以防止出現意外結果。
> [name=小蜜蜂]
## 7.3 Pointer Operators
### 7.3.1 The Address （&）Operator
- 位址運算子（**address operator**）是一元運算子，使用 & 可以知道 ==儲存變數的記憶體的位址==，而這符號要寫在變數的前面。
```c=
int y = 5;
int *yPtr;
yPtr = &y;
```
說明：將變數 y 的地址分配給指標變數 yPtr。然後變數 yPtr 被稱為**指向**y。
![](https://i.imgur.com/QN9m4z6.png)
### 7.3.2 Pointer Representation in Memory
![](https://i.imgur.com/3MBGnAw.png)
說明：上圖顯示了前一個指標在內存中的表示，假設整數變數 y 存儲在位置 600000，指標變數 yPtr 存儲在位置
500000 位址運算子的操作數必須是變數。 ==位址運算子不能應用於常數或算式!==
### 7.3.3 The Indirection （\*）Operator
- 間接參照運算子（**Indirection Operator**）是一元運算子，使用於要==從指標來追朔變數的值時==，對於指標要使用 * 這個運算子
```c=
printf("%d", *yPtr);
```
說明：使用 * 符號，可以得知**指標所指向之變數的值**
>**向指標所指向之變數取值**（**Dereferencing a pointer**）這個動作**尚未正確初始化**或**尚未分配的指標**==指向內存中的特定位置==是錯誤的。 這可能會導致致命的執行時間錯誤，或可能會意外修改重要數據並允許程序運行以錯誤的結果完成。
> [name=小蜜蜂]
### 7.3.4 Demonstrating the & and * Operator
![](https://i.imgur.com/s72DFE5.png)
說明： 
- printf 轉換說明符 %p 在大多數平台上將內存位置輸出為十六進制整數。
- 輸出中 a 的地址和 aPtr 的值相同，從而確認地址的 a 確實分配給了指標變數 aPtr（第 8 行）。
- & 和 * 運算符是互補的——當它們都連續應用於 aPtr 時
![](https://i.imgur.com/GFCoWK2.png)
說明：
上圖是到目前為止討論出來的運算符的==優先權（**Precedence**）和結合性（**Associativity**）==
## 7.4 Passing Arguments to Functions by Reference
- 要傳遞給函數的資料稱為**引數**（argument），有兩種方式**傳回值**（pass-by-value）以及**傳址**（pass-by-reference），然而，==C 中的所有引數（arguement）都是依靠傳回值。==
- return 可用於**返回一個**從被呼叫函數到呼叫者的值（或從被呼叫函數返回控制而**不需要傳回一個值**）。**傳址**也可用於使函數能夠**返回**通過修改呼叫者中的變數為其呼叫者**提供多個值**。
### 7.4.1 Use & and * to Accomplish Pass-By-Reference
- 使用**指標和間接運算符**來完成傳址。當呼叫帶有應修改參數的函數時，引數（**argument**）的地址通過。這通常透過應用位址運算子（**&**）來完成到其值將被修改的變數（在呼叫者中）。
### 7.4.2 Pass-By-Value
![](https://i.imgur.com/25WgvBi.png)
說明：第 14 行傳遞變數按值到函數cubeByValue。cubeByValue 函數將它的引數和使用 return 語句將新值傳回 main。新值分配給 main 中的編號（第 14 行）。
### 7.4.3 Pass-By-Reference
![](https://i.imgur.com/zCGc8ex.png)
![](https://i.imgur.com/RmR681y.png)
說明：變數 number 透過傳址（第 15 行），number 的地址是傳遞給函數cubeByReference。函數cubeByReference 作為==參數==（**parameter**）一個指向名為 nPtr 的 int 的指標（第 21 行）。 該函數**向指標所指向的變數取值**和量nPtr 指向的值的體積（第 23 行），然後將結果分配給 \*nPtr（實際上是 number in main），從而改變 main 中 number 的值。
### 7.4.4 Use a Pointer Parameter to Receive an Address
```c=
void cubeByReference(int *nPtr)
```
- 接收地址作為引數的函數必須定義一個指標參數來接收。
說明：標頭指定cubeByReference 接收整數變數的地址作為引數（**argument**），將地址本地存儲在 nPtr 中並且不傳回值。
### 7.4.5 Pointer Parameters in Function Prototypes
-  C 編譯器會**忽略**用於文檔目的的名稱
### 7.4.6 Functions That Receive One-Dimensional Arrays
- 對於需要一維陣列作為引數的函數，函數的原型和標頭**可以使用函數參數列表中**顯示的指標符號。
- 編譯器**不區分**函數接收一個指標和一個接收一維陣列的指標。這當然意味著函數必須**知道**它何時接收一個陣列或一個執行傳址的變數。當編譯器遇到函數參數時對於
```c=
int b[]
```
形式的一維陣列，編譯器將參數轉換為指向指標符號
```c=
int *b
```
這兩種形式可以互換（**interchangeable**）。
>除非呼叫者明確要求，否則使用傳回值（pass-by-value）引數給函數被呼叫函數在呼叫者的環境中修改參數變數的值。這可以防止意外修改呼叫者的參數，這是另一個例子==最小權限原則==。
> [name=小蜜蜂]
![](https://i.imgur.com/OMQjZlk.png)
![](https://i.imgur.com/BHtvpWs.png)
以上兩張圖參考看看吧
## 7.5 Using the const Qualifier with Pointers
- const 限定符使您能夠通知編譯器不應該修改特定變數的值。
>const 限定符可用於在軟體設計中**強制執行最小權限原則設計**。這可以減少除錯（**Debugging**）時間並防止意外的副作用（Side effects），使程序更易於修改和維護。
> [name=小蜜蜂]
### 7.5 preface const Values and Parameters
- 在第 5 章解釋了 C 中的所有函數呼叫都是按值傳遞（**pass-by-value**）引數的副本在函數呼叫中進行並傳遞給函數。如果副本在函數，呼叫者中的原始值不會改變。
- 在許多情況下，傳遞的值對一個函數進行修改，以便該函數可以完成其任務。然而，在某些情況下，==不應在被調用函數中更改該值==，即使它僅操作原始值的副本。
- 考慮一個將一維數組及其大小作為參數的函數，印出陣列。這樣的函數應該遍歷數組並輸出每個數組元素個別。在函數體中使用陣列的大小來確定何時循環應該終止。==陣列的大小及其內容**都不應該**改變函數本體。==
>如果一個變數在傳遞給它的函數體中沒有（或不應該）改變，該變數應該宣告為 const 以確保它不會被意外修改。
> [name=小蜜蜂]
>不知道函數需要指標作為傳遞引用的引數並傳回值。一些編譯器認為這些值是指標並將這些傳址。在運行時，存取記憶體（memory-access）違反或分段錯誤經常產生。其他編譯器捕獲之間的類型不匹配參數和參數並生成錯誤消息。
> [name=小蜜蜂]
- 指向非常數數據的非常數指標。
- 一個指向非常數數據的常數指標。
- 一個指向常數數據的非常數指標。
- 指向常數數據的常數指標。
==四種組合中的每一種都提供不同的**存取權限**==
### 7.5.1 Converting a String to Uppercase Using a Non-Constant Pointer to Non-Constant Data
- 最高級別的數據存取權限由指向非常數數據的非常數指標授予。這種情況下，可以通過解引用的指標修改數據，指標可以修改為指向其他數據項。一個非常數指標的宣告非常數數據不包括const。這樣的指標可用於接收字符串作為處理（並可能修改）每個字符的函數的參數細繩。
![](https://i.imgur.com/OHASu0h.png)
說明：函數 convertToUppercase 宣告了它的參數，一個非常數指向稱為 sPtr (char *sPtr) 的非常數數據的指針，位於第 19 行。該函數處理一次一個字符的數組字符串（由 sPtr 指向）。
- C標準庫函數調用 <ctype.h> 標頭中的 toupper（第 22 行）將每個字符轉換為其對應的大寫字母。如果原始字符不是字母或已經是大寫字母，toupper 返回原始字符。第 23 行將指針移動到下一個字符字符串。
### 7.5.2 Printing a String One Character at a Time Using a Non-Constant Pointer to Constant Data
- 指向常數數據的非常數指標可以修改為指向任何數據項適當的類型，**但它指向的數據不能被修改**。這樣的指標可能用於接收一個函數的數組參數，該函數將處理每個元素而無需修改那個元素。
![](https://i.imgur.com/eJz1tdG.png)
說明：函數 printCharacters 宣告參數 sPtr 的類型為 const char *（第 21 行）。宣告從右到左閱讀因為 sPtr 是指向字符常量的指針。函數使用for語句輸出字符串中的每個字符，直到遇到空字符。每個字符後印出，指標 sPtr 遞增，這使指標移動到下一個字符在字符串中。
![](https://i.imgur.com/t9EfPQr.png)
說明：編譯接收非常數的函數的嘗試指向常量數據的指標 xPtr 。 這個函數試圖修改指向的數據，第 18 行中的 xPtr ，這會導致編譯錯誤。
- 陣列是聚合數據類型，用於**存儲相同**的相關數據項。
- 一個結構是能夠以一個名稱存儲相同或不同數據類型的相關數據項。當一個函數被呼叫時，以陣列作為引數，陣列會自動通過引用傳遞給函數。然而，結構總是按值傳遞，傳遞整個結構的副本。這需要複製結構中每個數據項的執行時間開銷並將其存儲在計算機的函數調用堆棧中。**當結構數據必須是傳遞給一個函數**，我們可以使用指向常數數據的指標來獲得 passby 的性能，傳址和傳值的保護。
### 7.5.3 Attempting to Modify a Constant Pointer to Non-Constant Data
![](https://i.imgur.com/YwZr5oZ.png)
說明：指向非常數數據的常數指標始終指向相同的內存位置，並且可以通過指標修改該位置的數據。這是任何陣列姓名都不履行。
- 陣列名稱是指向數組開頭的常數指標，一個常數指向非常數數據的指標可用於接收陣列作為函數的參數。
    - 僅使用數組索引符號存取陣列元素。宣告的指標 const 必須**在定義時初始化**（如果指標是函數參數，則為用傳遞給函數的指標初始化）。
    - 嘗試修改一個常量指標。指標 ptr 在第 12 行定義為 int * const 類型。定義從右到左讀作 ptr 是一個指向整數的常數指標。指標是用整數變數 x 的位址初始化（第 12 行）。該程式嘗試分配 y 到 ptr 的地址（第 15 行），但編譯器產生錯誤。
### 7.5.4 Attempting to Modify a Constant Pointer to Constant Data
- 最小訪問權限由指向常數數據的常數指標授予
- 這樣的指標總是指向同一個內存位置，該內存位置的數據不能被修改
- 用來傳給只看不改的函數（唯讀）
![](https://i.imgur.com/nZXB6iD.png)
![](https://i.imgur.com/n2Cvs4A.png)
說明：
- 圖 7.14 定義指標變數 ptr（第 13 行）的類型為 const int \*const，從右到左讀作 ptr 是指向整數常數的常數指標
    - 當試圖修改 ptr 指向的數據（第 16 行）並且當嘗試修改存儲在指針變量中的地址時（第 17 行），該圖顯示了生成的錯誤消息。


## 7.6 Bubble Sort Using Pass-by-Reference
- bubbleSort 範例中的 swap 運用傳址
```c=
void bubbleSort(int * const array, const size_t size)
{
    void swap(int *element1Ptr, int *element2Ptr);
    ...
        if (array[j] > array[j + 1]) {
            swap(&array[i], &array[j + 1]);
        }
}
void swap(int *element1Ptr, int *element2Ptr)
{
    int hold = *element1Ptr;
    *element1Ptr = *element2Ptr;
    *element2Ptr = hold;
}
```
- 以上為部分程式碼
- C 強制執行函數之間的變數相互隱藏，因此 swap 無權訪問 bubbleSort 中的變數
- 但 bubbleSort 想要交換兩個位置的變數值，所以只能傳址
- 在 swap 中，記得要用 \* dereferenced，否則是指標互換（無意義）
1. 關於 bubbleSort 函數的陣列參數
- 將陣列宣告為 int * const array 而不是 int array[] 
    - 參數被宣告為 const 以強制執行**最小權限原則**
        - 因為不需要修改
2. 關於 swap 函數在 bubbleSort 函數中的 prototype
- 因為只有 bubbleSort 要用 swap 所以在裡面宣告
    - 在一個函數中宣告另一個函數會導致其被綁定在前函數中
    - 在 swap 宣告之前宣告的其他函數試圖呼叫 swap 則無法訪問到正確的函數 prototype
        - 因此編譯器會自動生成一個回傳值為 int （預設）的 prototype，通常導致與函數定義不匹配，並產稱編譯警告或錯誤
>將函數 prototype 放在其他函數的定義中，通過將正確的函數調用限制在 prototype 出現的函數中來強制執行最小權限原則
>[name=小蜜蜂]
3. 關於 bubbleSort 函數的 size參數
- 不知道陣列大小不能進行排序，所以你要傳
- 另一個常用方法是傳陣列的開頭以及結尾的指標（7.8）
- 好的軟體要能適應資料變化（以這題來說就是給我幾個數我都可以排的意思）
>將陣列傳遞給函數時，還要傳遞陣列的大小。這有助於使該函數可重複在許多程式中使用
>[name=小蜜蜂]
- 可將陣列的大小存在一個全域變數中，這會更方便，因為不用將大小的副本傳給函數
    - 但是，其他需要做整數陣列排序的函數可能沒有相同的全域變數，因此該函數不能在那些程式中使用
>全域變數通常違反最小權限原則，並可能導致軟體工程上的麻煩。全域變數應該只用於表示真正共享的資源，例如一天中的時間。
>[name=小蜜蜂]
- 陣列的大小可直接寫到函數中（寫死）
    - 這個將函數的使用限制為特定大小的數組並明顯減少其可重用性
    - 在僅處理特定大小的一維整數陣列的程式中，即可使用該函數

## 7.7 sizeof Operator
- sizeof 用來定義一個陣列（或其他的類型）有幾 bytes
> sizeof 是在編譯時進行運算，所以不會占用到執行時的資源
> [name=小蜜蜂]
```c=
double real[22];
sizeof(real) / sizeof(real[0]); // == 22
```
- 可用 全陣列大小 / 單位類型大小 來取得這個陣列共有幾個元素
- 當 sizeof() 中放指標時，它會回傳指標大小，非陣列大小
- 指標在 Windows 和 Linux 系統佔用 4 bytes，在 Mac 佔 8 bytes *~~空間管理大師~~*
- 環境不同時，同個類型有可能大小不同
>用於存儲特定數據類型的 bytes 數量可能因系統而異。在編寫依賴於數據類型大小並且需要在多台電腦上運行時，使用 sizeof 來確定用於存儲數據類型的 bytes 數。
>[name=小蜜蜂]

## 7.8 Pointer Expressions and Pointer Arithmetic
### 7.8.1 Allowed Operators for Pointer Arithmetic
- 指標可以像其他類型一樣進行以下運算
    - `++ -- + += - -=`
- 一個指標可以從另一個指標中減去
    - 只有當兩個指標都指向同一個陣列的元素時才有意義
### 7.8.2 Aiming a Pointer at an Array
- 語法介紹
```c=
vPtr = v;
vPtr = &v[0];
```
上方兩者 vPtr 皆是指向 v 陣列的第一個元素
![](https://i.imgur.com/YL63x4C.png)
```c=
vPtr += 2;
vPtr++;
--vPtr;
```
上方運算皆是將 vPtr 指標進行移動，往後移動兩個 ==元素== （非位元或位元組），遞增一個元素 及 遞減一個元素
>因為指標運算的結果取決於指標指向的物件的大小，所以指標運算取決於各自的機器和編譯器。
>[name=小蜜蜂]
### 7.8.3 ~ 7.8.5 + - ++ -\- an Interger to a Pointer
![](https://i.imgur.com/rKJhp4Q.png)
- 若 vPtr 是一個指向 int 的指標，那 vPtr += 2 即是上方圖所示
    - vPtr + n，location 就會加 sizeof(int) * n
- 若 vPtr 是一個指向 char 的指標，那 vPtr += 2 後的位址會是 3002
- vPtr + n，location = vPtr + sizeof(var) * n
- 減法和遞增遞減同理，白話來說就是指標在陣列上滑來滑去，玩大風吹

### 7.8.6 Subtracting One Pointer from Another
- **兩個指標相減只有在同一陣列中才有意義**
    - 聲明：以下的 兩指標相減 皆是在同一陣列中進行
- 兩指標相減得到的並非是兩指標位元組差，而是兩指標的元素相差個數
```c=
int v[5];
int *vPtr = v;
int *vPtr2 = &v[2];
printf("%d\n", vPtr2 - vPtr); // not 8(bytes) but 2(elements)
```
>不應對不指向在同一個陣列的指標做運算
>[name=小蜜蜂]
### 7.8.7 Assigning Pointers to One Another
- 同型態之間的指標可以相互進行指派
    - 不同型態之間的指標不能直接進行指派，除非...
- ~~萬能的指標不存...~~ void 指標 (pointer to void) void*
    - 可以接受各種型態的指標
    - 可以指派給各種型態的指標
    - 不需要強制轉型
### 7.8.8 Pointer to void
- 不能 dereferenced void 指標
    - 因為編譯器不確定你一個資料的 bytes 數
>兩個指標中，如果沒有一種類型的指標是 void* 類型，則將其中一個類型的指標分配給另一個類型的指標會導致語法錯誤
>[name=小蜜蜂]

>Dereferencing void* 指標會導致法錯誤
>[name=小蜜蜂]
### 7.8.9 Comparing Pointers
- 比較兩個不指向同一個陣列的指標無意義
- 兩指標進行比較是比較兩者的位址，例如：一方位址編號較另一方大
- 常用來比較是否為 NULL
>不應比較不指向同一陣列中元素的兩個指標
>[name=小蜜蜂]

## 7.9 Relationship between Pointers and Arrays
- 陣列和指標在 C 語言中密切相關，通常可以互換使用
- 陣列名稱可以被認為是一個常數指標
- 指標可用於執行任何涉及陣列索引(index) 的操作
```c=
int b[5];
int *bPtr;
bPtr = b; // == bPtr = &b[0];
```
### 7.9.1 Pointer/Offset（偏移） Notation
- b[3] 為以下含意，ps. [] 為下標運算子
```c=
*(bPtr + 3)
```
- 表達式中的 3 是指標的偏移量
- 當 bPtr 指向陣列的第一個元素時，偏移值等同陣列的索引值
- ==括號是必要的==，因為 * 的優先級高於 + 的優先級
    - 如果沒有括號，上面的表達式會將表達式的值加 3
```c=
&b[3] == bPtr + 3
b[3] == *(b + 3) == *(bPtr + 3)
```
- 單純 b，後面不加任何運算，仍然指向陣列中的第一個元素
### 7.9.2 Pointer/Index Notation
- 陣列指標也可以像陣列般用[] （畢竟兩個就一樣的東西，不同表示方式而已，~~一個效果，各自表述(?~~）
```c=
bPtr[1]
```
### 7.9.3 Cannot Modify an Array Name with Pointer Arithmetic
- 陣列名稱相似於常數指標 (constant pointer)，故不可運算
:::danger
b += 3
:::
上方程式無效
>嘗試使用指標算法修改陣列名稱的值會導致編譯錯誤
>[name=小蜜蜂]
### 7.9.4 ~ 7.9.5 Examples
Example, self-reading. 請自行體驗指標與陣列之間的奧妙

## 7.10 Arrays of Pointers
- 陣列可儲存指標(array of pointers)
    - 常用來存字串(array of strings)
        - 簡稱字串陣列(string array)
- 陣列中的每個元素皆是字串
    - 但對於 C 語言，字串其實是一個指標指向字串的第一個字

![](https://i.imgur.com/84ddxjZ.png)
```c=
const char *suit[4] = {"Hearts", "Diamonds", "Clubs", "Spades"};
```
- 實際存於陣列中的只有指標，分別指向各個字串的第一個字
- 因為指是指標，所以雖然陣列大小不可改變(4)，但字串的長度卻不受此限
- 是在 C 語言中，強大且靈活的數據結構能力 ~~os.有這麼簡單就好了，課本真單純啊~~
- 可節省空間
    - 不用給每個字串開到最大字串的個數
### 個人補充 [選讀]
- 如果行有餘力的話可以當小說看看(?
- 指標字串陣列遠沒書上寫的這麼簡單，以書上的例子延伸來說
```c=
#include <stdio.h>

int main()
{
    char *suit[4] = {"Hearts", "Diamonds", "Clubs", "Sqades"};
    suit[0] = "HEARTSTEST"; // available
    for(int i = 0; i < 4; i++){
        printf("%s ", suit[i]);
    }
    puts("");
    
    suit[0][1] = 'T'; //fail，如果前面 char *suit[4] 打上 const，編譯器會報錯，不打為未定義行為
    for(int i = 0; i < 4; i++){
        printf("%s ", suit[i]);
    }
    puts("");
    
    scanf("%s", suit[0]); //fail，就算前面打上 const，編譯器一樣不報錯
    for(int i = 0; i < 4; i++){
        printf("%s ", suit[i]);
    }
    puts("");
    return 0;
}
```
- 只要用 `char *` 來存字串，全是唯讀，相當於 `const char *`，（除非是指到某個字串陣列或者有分配記憶體給它），能整個字串換掉（因為是位址），但不能修改字串中的東西，也不能用於 `scanf`

如果覺得頭暈的話只要大概知道有這個問題就好，有興趣的可以再深入研究或者來討論都可以，我在筆記這裡就點到為止
## 7.11 Case Study: Card Shuffling and Dealing Simulation
Example, self-reading. Random 與 指標陣列的結合運用

## 7.12 Pointers to Functions
- 指向函數的指標即是指向函數在內存中的位址
- 函數名稱實際上就是程式執行時，內存中函數的起始位址
- 指向函數的指標可以在函數之間傳遞
### 7.12.1 Sorting in Ascending or Descending Order
- 函數 bubble 接了一個函數指標，用來作為判斷是要升序還是降序排序的依據
>詳細過程請自行參悟，我這裡整理語法
1. 在定義函數時
```c=
void bubble(int work[], size_t size, int (*compare)(int a, int b)){}
```
- 用來告訴 bubble 有一個函數指標參數傳入
- int (\*compare)(int, int) 為必須，不可省，參數名稱可省
    - int (\*compare) 的括號不可省略，否則將認為是傳入兩個 int 的函數，並回傳 int 指標，而非函數指標
2. 在宣告函數時
- prototype 中，可寫為
```c=
int (*)(int, int)
```
- 省略函數名稱及參數名稱
3. 在使用函數時
- 跟一般指標相同，一樣使用 * 來 dereferenced
```c=
if ((*compare)(work[count], work[count + 1]))
```
- 不 dereferenced 也可
```c=
if (compare(work[count], work[count + 1]))
```
- 但前者較佳，可讓讀者更清楚的知道這個函數是被傳過來的，而不是直接呼叫的函數
### 7.12.2 Using Function Pointers to Create a Menu-Driven System
- 可將函數指標放入指標陣列來呼叫函數，以達成清單效果
```c=
void function1(int a);
void function2(int b);
void function3(int c);
int main(void)
{
    int choice;
    void (*f[3])(int) = { function1, function2, function3 };
    while (~scanf("%d", &choice) && choice >= 0 && choice < 3) {
        (*f[choice])(choice);
    }
    return 0;
}
```
- 以上為部分程式碼
- 以函數陣列的方式可以替代部分 if...else 及 switch case

## 7.13 Secure C Programming
- **printf_s**、**scanf_s** 和其他安全函數
    - printf_s 和 scanf_s 等函數因為有**運行時限**使的它們更加安全
    - 它們要求的指標參數是非空 (!NULL)
    - 任何 NULL 指標參數都被視為違反規則，導致函數失敗並返回狀態通知
    - 在 scanf_s 中，如果任何指標參數（包括格式控制字串）為NULL，函數返回EOF
    - 在 printf_s 中，如果格式控制字串或對應於 %s 的任何參數為 NULL，該函數停止輸出數據並返回一個負數
- 關於指標的其他 CERT 指南
    - 濫用指標為當今系統中最常見的安全漏洞
    - CERT 提供了各種指南來幫助預防此類問題
    - CERT C Secure 編碼標準
        - 有興趣的話可以自己讀囉

###### tags: `程設好難` `學校門口大樹石頭下` `他的課就很好睡阿`
<style>
.navbar-brand::after { content: " × 老葉的程式設計"; }
</style>