Semorphe

指標

一個裝著「地址」的變數。 · ⏱ 約 40 分鐘

你會學到三件事

  1. & 拿地址、* 去那個地址取東西
  2. 為什麼 scanf 非要那個 & 不可
  3. 指標不是難,是多了一層

開始之前

到目前為止,一個變數裝的是值。

這一課的變數裝的是「另一個變數在哪裡」。

🟢 先把編輯器打開,跟著下面一起做:跟著做

一、兩個符號

這兩個符號長得像,做的事不一樣:

1int n = 42;2int *p = &n;
42變數n取得的位址 (&)建立int(整數)指標變數p初始值(*)宣告int(整數)變數n=12
唸作 意思
&n address of n n 在哪裡(一個地址)
int *p p 是一個指向 int 的指標 p 裝的是地址
*p 去 p 指的地方 那個地址上的東西(也就是 n)

⚠️ * 出現在兩個地方,意思不一樣:

1int *p = &n;     // 宣告時的 * =「p 是指標」2*p = 100;        // 使用時的 * =「去那裡」
變數n取得的位址 (&)100把存到p指向的位置 (*)建立int(整數)指標變數p初始值(*)12

這是初學者最容易糊掉的一點。記法: 宣告時的星號在講型別,使用時的星號在做動作。

積木盤上那三顆分別寫著「建立 … 指標變數 … 初始值 …」、 「取得 … 指向的值」與「把 … 存到 … 指向的位置」 ——⚠️ 積木上沒有星號,它把那個符號翻譯成了話。

二、一張圖

畫成圖是這樣:

    n                      p
 ┌──────┐              ┌──────┐
 │  42  │  ◄────────── │ 0x7f │
 └──────┘              └──────┘
  位址 0x7f              裝著 n 的位址
1printf("%d\n", *p);   // 42   ——去 0x7f 看,是 422*p = 100;             // 去 0x7f,把它改成 1003printf("%d\n", n);    // 100  ← 🔴 n 也變了
變數p取得指向的值 (*)100變數n格式化輸出 (printf)%d\n,把存到p指向的位置 (*)格式化輸出 (printf)%d\n,引入函式庫cstdio(C++ 標準輸入輸出)123

n 和 *p 是同一個東西的兩個名字。

三、🔴 所以 scanf 需要 &

第 1 課那個沒解釋的 &,現在可以解釋了。

所以 scanf 要的是「位址」:

🔎 這一段是試一下——打了看結果就好,不要留在程式裡。

1scanf("%d", &n);
變數n格式化輸入 (scanf)%d,引入函式庫cstdio(C++ 標準輸入輸出)1

scanf 要改變 n,而 C 傳參數的時候是傳值: 傳 n 進去的話,函式拿到的是一份複本,改它不會影響外面。

所以要傳「n 在哪裡」,函式才改得到本體。

& 不是 scanf 的怪癖,是 C 唯一能「改到外面」的方式。

⚠️ 這也是為什麼 scanf("%s", s) 不用 &: s 是陣列,而陣列名本身就已經是位址了。

四、空指標

NULL 表示「這個指標現在不指向任何東西」:

🔎 這一段是試一下——打了看結果就好,不要留在程式裡。

1int *p = NULL;      // 指向「什麼都沒有」
NULL建立int(整數)指標變數p初始值(*)1

⚠️ 對 NULL 用 * 會讓程式當場當掉(segmentation fault)。

而那其實是好消息:一個指到亂七八糟地方的指標會安靜地毀掉別的東西, 而 NULL 至少會立刻停下來。

用之前先問:

1if (p != NULL) {2    printf("%d\n", *p);3}
變數pNULL≠變數p取得指向的值 (*)格式化輸出 (printf)%d\n,如果則引入函式庫cstdio(C++ 標準輸入輸出)12

完成的樣子

這一課的參考做法——程式碼每一行的號碼,就印在對應的那塊積木上。

1#include <cstdio>2using namespace std;3int main() {4    int n = 42;5    int *p = &n;6    printf("%d\n", *p);7    *p = 100;8    printf("%d\n", n);9    return 0;10}
42變數n取得的位址 (&)變數p取得指向的值 (*)100變數n0回傳格式化輸出 (printf)%d\n,把存到p指向的位置 (*)格式化輸出 (printf)%d\n,建立int(整數)指標變數p初始值(*)宣告int(整數)變數n=定義函式 回傳型別int(整數)main使用命名空間std引入函式庫cstdio(C++ 標準輸入輸出)123456789

換你了

寫一個函式 void swap(int *a, int *b),把兩個變數的值對調。

⚠️ 呼叫的時候要寫 swap(&x, &y)。不加 & 的話它交換的是複本, 而外面兩個變數紋風不動,程式不會報錯。

(這是 C 的經典練習,而它就是「為什麼需要指標」的答案。)

到編輯器做這一題:練習:寫一個 swap(int *a, int *b)

做一個

寫一個 void divmod(int a, int b, int *q, int *r),把商和餘數分別寫回去。 用 divmod(47, 5, &q, &r) 叫它,印出 q 和 r。

🔴 一個函式只能 return 一個值,而這一題要兩個。指標就是那個出口。 ⚠️ 把 *q = ... 寫成 q = ... 會編得過而什麼都沒發生,試一次看看。

到編輯器做這一題:做一個:一次回傳兩個答案

這一課你做了什麼

如果卡住了

你看到 多半是因為
程式當掉(segfault) 對 NULL 或沒初始化的指標用了 *
swap 沒有效果 呼叫時忘了 &,或函式參數沒寫 *
scanf 之後值沒變 忘了 &
印出一個很長的數字 印的是位址不是值。要 *p
宣告時的 * 到底屬於誰 int *p 和 int* p 完全一樣。⚠️ 而 int* a, b; 裡 b 不是指標
在編輯器打開這一課 →