指標和參考是 C++ 最重要也最容易混淆的概念之一。理解它們是掌握動態記憶體管理、多型、資料結構的基礎。

建議授課時數:2 小時(含 10 分鐘休息)


學習目標

  1. 理解指標的概念:位址、宣告、解參考
  2. 掌握 &(取址)和 *(解參考)運算子
  3. 理解 nullptr 與空指標安全
  4. 認識 const 指標的各種組合
  5. 學會指標算術與陣列的關係
  6. 理解參考(reference)的語意與用法
  7. 區分指標與參考的適用場景
  8. 掌握函式指標的宣告、使用與 callback 模式
  9. 認識 std::function 作為現代替代方案
  10. 理解 C 風格字串、二維陣列與指標的關係
  11. 掌握 qsort / std::sort 的 callback 用法與指向成員的指標
  12. 認識 RAII 與智慧指標unique_ptr / shared_ptr / weak_ptr)作為現代記憶體管理基礎

課程時間表(120 分鐘)

時段 主題 內容 範例
0:00–0:25 指標基礎 位址概念、宣告、解參考、nullptr pointers.cpp §1–3
0:25–0:40 const 指標 & 函式參數 四種 const 組合、swap 範例 pointers.cpp §4–5
0:40–0:55 指標算術 遞增、指標差、陣列走訪、雙指標法 pointer_arithmetic.cpp
0:55–1:05 休息
1:05–1:20 參考 別名、const 參考、回傳參考 references.cpp
1:20–1:35 函式指標 宣告、callback、std::function function_pointers.cpp
1:35–1:50 進階指標 C 字串、二維陣列、qsort/sort、指向成員 advanced_pointers.cpp
1:50–2:05 智慧指標 RAII、unique/shared/weak_ptr、擁有權 smart_pointers.cpp
2:05–2:15 總結 & 練習 常見陷阱、比較表、課堂練習

建議每 15–20 分鐘穿插一次現場 demo小測驗,維持學員專注力。 若時間不足,「進階指標」與「智慧指標」可視班級程度選擇性講授或作為延伸內容。


一、什麼是指標?

1.1 核心概念

指標是一個儲存記憶體位址的變數。一般變數存「值」,指標存「某個值所在的位置」。

int x = 42;
int* ptr = &x;   // ptr 儲存 x 的位址

std::cout << x;       // 42(值)
std::cout << &x;      // 0x7fff...(位址)
std::cout << ptr;     // 0x7fff...(與 &x 相同)
std::cout << *ptr;    // 42(解參考:取得該位址的值)
std::cout << &ptr;    // ptr 自身所在的位址(不同於 ptr 儲存的值)

1.2 記憶體示意圖

變數名稱    位址           值
  x      [0x1000]       42
  ptr    [0x1008]       0x1000  ←─ 指向 x 的位址

1.3 為什麼需要指標?

用途 說明
間接修改 函式透過指標修改呼叫者的變數
動態配置 new / delete 在執行期分配記憶體
資料結構 鏈結串列、樹等節點以指標串接
效能 傳遞大型物件的位址,避免複製
多型 透過基底類別指標操作衍生類別物件

二、宣告指標

int*    intPtr;       // 指向 int 的指標
double* dblPtr;       // 指向 double 的指標
char*   charPtr;      // 指向 char 的指標

int* p1, p2;          // ⚠ 只有 p1 是指標,p2 是 int!
int *p3, *p4;         // ✓ p3 和 p4 都是指標

指標的大小

在同一平台上,所有型別的指標大小相同(64-bit 系統通常 8 bytes):

sizeof(int*)    == sizeof(double*) == sizeof(char*) == sizeof(void*)
// 通常都等於 8(64-bit)或 4(32-bit)

指標的大小取決於位址匯流排寬度,而非指向的型別。


三、nullptr vs NULL vs 0

int* ptr = nullptr;   // 空指標(C++11 推薦)

if (ptr != nullptr) {
    std::cout << *ptr;  // 只在非空時解參考
}

三者的歷史與差異

寫法 本質 問題
0 整數常數 0 既是數字又能當空指標,語意模糊
NULL 巨集,通常定義為 00L 本質仍是整數,與 0 同樣有歧義
nullptr 關鍵字,型別為 std::nullptr_t 無歧義,C++11 起的唯一正解

歷史上 C 用 NULL(多半 #define NULL 0)表示空指標。但 NULL 骨子裡是整數 0,在 C++ 的多載與模板中會造成錯誤的函式選擇

void f(int x)  { std::cout << "f(int)\n"; }
void f(int* p) { std::cout << "f(int*)\n"; }

f(0);        // 呼叫 f(int) —— 0 是整數
f(NULL);     // 多半也呼叫 f(int)!因為 NULL 其實是 0(不是你要的指標版本)
f(nullptr);  // ✓ 明確呼叫 f(int*),因為 nullptr 只能是指標

C++ Core Guidelines ES.47:用 nullptr 而非 0NULLnullptr 型別明確、可讀性高,且不會被誤當成整數。


四、const 與指標

int x = 10;
int y = 20;

// 1. 指向 const 的指標:不能透過指標修改值
const int* p1 = &x;
// *p1 = 99;  ← 錯誤!
p1 = &y;      // ✓ 可以改指向

// 2. const 指標:不能改指向
int* const p2 = &x;
*p2 = 99;     // ✓ 可以改值
// p2 = &y;   ← 錯誤!

// 3. const 指標指向 const:都不能改
const int* const p3 = &x;
// *p3 = 99;  ← 錯誤!
// p3 = &y;   ← 錯誤!

口訣const* 左邊 → 值不能改;const* 右邊 → 指向不能改。

四種組合速查表

宣告 改值 改指向 常見用途
int* p 一般用途
const int* p 唯讀存取(函式參數)
int* const p 固定指向某變數
const int* const p 完全唯讀

五、指標作為函式參數

延續 Ch04 的參數傳遞,指標是第三種方式:

// 傳值 — 複製,不影響原始值
void byValue(int x)       { x = 0; }

// 傳參考 — 別名,直接修改
void byRef(int& x)        { x = 0; }

// 傳指標 — 傳位址,需解參考
void byPtr(int* x)        { *x = 0; }

int main() {
    int n = 42;
    byValue(n);   // n 仍為 42
    byRef(n);     // n 變為 0
    byPtr(&n);    // n 變為 0(呼叫時需加 &)
}

swap 範例

void swapByPtr(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 呼叫:swapByPtr(&m, &n);

課堂討論:指標參數 vs 參考參數,何時選哪個?


六、指標算術

int arr[] = {10, 20, 30, 40, 50};
int* p = arr;       // 陣列名就是第一個元素的位址

std::cout << *p;       // 10
std::cout << *(p + 1); // 20
std::cout << *(p + 2); // 30

p++;                   // p 移動 sizeof(int) 個位元組
std::cout << *p;       // 20

陣列與指標的關係

arr[i]   等同於  *(arr + i)
&arr[i]  等同於  arr + i

指標算術規則

運算 說明 範例
p + n 向前移 n 個元素 *(arr + 2)arr[2]
p - n 向後移 n 個元素
p++ / p-- 移動一個元素 走訪陣列
p1 - p2 兩指標間的元素個數差 end - begin
p1 < p2 比較位址先後 同一陣列內有效

重要限制

  1. 指標算術只在連續記憶體(陣列)中有意義
  2. 不可對不相關的指標做減法或比較
  3. 指向「最後元素 + 1」可用作迴圈邊界,但不可解參考
  4. 越界存取是未定義行為(UB)

雙指標法反轉陣列

void reverseArray(int* arr, int size) {
    int* left  = arr;
    int* right = arr + size - 1;
    while (left < right) {
        int temp = *left;
        *left = *right;
        *right = temp;
        left++;
        right--;
    }
}

七、進階指標概念

7.1 指標的指標(Multi-level Pointer)

int x = 42;
int*  p  = &x;    // 指向 int
int** pp = &p;    // 指向 int*(二級指標)

std::cout << x;     // 42
std::cout << *p;    // 42
std::cout << **pp;  // 42(解參考兩次)

常見用途:二維陣列、函式修改指標本身、動態配置指標陣列。

7.2 void* 通用指標

int    i = 42;
double d = 3.14;

void* vp = &i;   // void* 可指向任意型別
// std::cout << *vp;  ← 錯誤!不能直接解參考

int* ip = static_cast<int*>(vp);  // 需轉型後才能使用
std::cout << *ip;  // 42

void* 常用於 C 函式庫(如 malloc)或需要型別無關的通用介面。

7.3 指向物件的指標(-> 運算子)

struct Point { int x, y; };

Point pt = {10, 20};
Point* ptr = &pt;

// 兩種等價寫法
(*ptr).x = 100;   // 先解參考再存取成員
ptr->x = 100;     // 箭頭運算子(更常用)
ptr->y = 200;

7.4 動態記憶體(預告 Ch16)

int* dyn = new int(42);   // 在 heap 分配
std::cout << *dyn;         // 42
delete dyn;                // 釋放
dyn = nullptr;             // 避免懸垂指標

現代 C++ 建議使用 std::unique_ptr / std::shared_ptr 管理動態記憶體。


八、參考(References)

int x = 42;
int& ref = x;    // ref 是 x 的別名

ref = 100;        // 修改 ref 等於修改 x
std::cout << x;   // 100

特性

  • 必須在宣告時初始化
  • 初始化後不能重新綁定到其他變數
  • &ref&x 相同(同一記憶體位置)
  • 不存在「空參考」

常見誤解:ref = b 不是重新綁定

int a = 10, b = 20;
int& ref = a;
ref = b;   // 這是把 b 的值賦給 a,不是讓 ref 改綁到 b!
// 結果:a = 20, b = 20

const 參考

const int& cref = x;   // 不能透過 cref 修改 x
// cref = 200;  ← 錯誤!

const int& temp = 42;  // ✓ const 參考可以綁定到暫時值(rvalue)

const 參考作為函式參數是 C++ 的最佳實踐:

void print(const std::string& s);  // 避免複製,且不修改原始值

回傳參考

int& getElement(std::vector<int>& vec, size_t i) {
    return vec.at(i);
}

// 使用:getElement(vec, 2) = 999;  // 直接修改 vec[2]

危險:不可回傳區域變數的參考!

int& bad() {
    int local = 42;
    return local;  // 懸垂參考!local 已銷毀
}

九、指標 vs 參考比較

特性 指標(int* 參考(int&
可為空值 是(nullptr
可重新指向
語法 *& 直接使用
初始化 可延後 必須立即
多級 支援(int** 不支援
算術 支援(p++ 不支援
用途 動態記憶體、可選參數 函式參數、運算子多載

何時使用哪個?

情境 建議
函式參數,確定不為空 參考 const T&
函式參數,可能為空 指標 const T*
需要重新指向 指標
動態記憶體 指標(或智慧指標)
陣列 / 緩衝區操作 指標
回傳容器元素 參考
與 C API 互操作 指標

十、函式指標(Function Pointers)

函式在記憶體中也有位址。函式指標儲存函式的位址,讓程式可以間接呼叫函式,或將函式作為參數傳遞(callback)。

10.1 宣告語法

// 回傳型別 (*指標名)(參數型別列表)
int (*funcPtr)(int, int);

funcPtr = add;              // 函式名自動轉為位址
int result = funcPtr(3, 5); // 間接呼叫
// 等價於 (*funcPtr)(3, 5)

解析技巧(*funcPtr) 被括號包住,表示 funcPtr 是一個指標;後面的 (int, int) 是參數列表。

10.2 typedef 簡化

typedef int (*BinaryOp)(int, int);

BinaryOp op = add;
std::cout << op(10, 20);  // 30

C++11 起也可用 type alias:

using BinaryOp = int (*)(int, int);

10.3 函式指標陣列

int (*operations[])(int, int) = {add, subtract, multiply, divide};

int x = 20, y = 4;
for (int i = 0; i < 4; i++) {
    std::cout << operations[i](x, y) << "\n";
}

這是策略模式的 C 風格實現,在嵌入式系統和圖形 API 中很常見。

10.4 函式指標作為 Callback

// 高階函式:接收一個函式指標
int apply(int a, int b, int (*op)(int, int)) {
    return op(a, b);
}

// 使用
apply(10, 3, add);       // 13
apply(10, 3, multiply);  // 30

典型應用場景

場景 說明
qsort C 標準庫排序,傳入比較函式
事件處理 GUI 按鈕點擊 callback
過濾 / 映射 對集合每個元素執行指定操作
插件系統 動態載入並呼叫函式

10.5 回傳函式指標

int (*selectOp(char op))(int, int) {
    switch (op) {
        case '+': return add;
        case '-': return subtract;
        default:  return add;
    }
}

auto func = selectOp('+');
func(5, 3);  // 8

10.6 函式指標 vs 函式參考

void callByPtr(int (*f)(int, int));   // 函式指標參數
void callByRef(int (&f)(int, int));   // 函式參考參數(較少見)

函式參考不能為 null,且語法較簡潔,但函式指標更通用。


十一、現代替代方案:std::function 與 Lambda

11.1 std::function

#include <functional>

int calculate(int* arr, int size,
              const std::function<int(int, int)>& op) {
    int result = arr[0];
    for (int i = 1; i < size; i++)
        result = op(result, arr[i]);
    return result;
}

std::function 是可儲存任意 callable 的泛型包裝器:函式指標、lambda、functor 皆可。

11.2 Lambda 與函式指標

// 無 capture → 可轉為函式指標
int (*fp)(int, int) = [](int a, int b) { return a + b; };

// 有 capture → 必須用 std::function
int factor = 2;
std::function<int(int, int)> fn =
    [factor](int a, int b) { return (a + b) * factor; };

11.3 選擇指南

機制 Overhead 支援 Capture 適用場景
函式指標 C 互操作、效能關鍵
std::function 小(可能 heap 配置) 通用 callback API
template + auto 零(編譯期) 內部高效能程式碼
lambda(無 capture) 簡單 inline 邏輯

初學階段先掌握函式指標;進階後以 std::function 和 lambda 為主。


十二、C 風格字串與字元指標

C++ 同時支援 std::string 與「C 風格字串」。C 字串本質是'\0'(空字元)結尾的 char 陣列,字串字面量在表達式中會退化為 const char*

const char* msg = "Hello";   // 指向唯讀字串字面量
//  'H' 'e' 'l' 'l' 'o' '\0'  ← 編譯器自動加上結尾的 '\0'

12.1 用指標走訪字串

走訪直到遇到結尾的 '\0'

for (const char* p = msg; *p != '\0'; ++p) {
    std::cout << *p << " ";
}

12.2 雙指標反轉字串

char buf[] = "ABCDEF";          // 可修改的字元陣列
char* left  = buf;
char* right = buf + std::strlen(buf) - 1;
while (left < right) {
    std::swap(*left, *right);    // 或手動交換
    ++left;
    --right;
}
// buf 變成 "FEDCBA"

注意const char* 指向的字串字面量唯讀,嘗試修改是未定義行為。要修改字串請使用 char[] 陣列。 現代 C++ 一般優先使用 std::stringchar* 主要用於 C API 互操作與底層處理。


十三、二維陣列與指標

二維陣列在記憶體中是連續排列(row-major,列優先)。

int matrix[2][3] = {{1, 2, 3},
                    {4, 5, 6}};

matrix[i][j]  等同於  *(*(matrix + i) + j)

指向「陣列」的指標

int (*rowPtr)[3] = matrix;   // 指向「含 3 個 int 的陣列」的指標

rowPtr[i][j];   // 等同 matrix[i][j]

辨析int (*p)[3](指向陣列的指標)與 int* p[3](指標陣列)是完全不同的東西! - int (*p)[3] → 一個指標,指向含 3 個 int 的陣列 - int* p[3] → 一個陣列,含 3 個 int* 指標


十四、callback 實戰:qsort、std::sort 與指向成員的指標

14.1 qsort(C 標準庫排序)

qsort 是函式指標 callback 最經典的應用。它型別無關(透過 void*),由你提供比較函式:

#include <cstdlib>

int ascendingCmp(const void* a, const void* b) {
    int x = *static_cast<const int*>(a);
    int y = *static_cast<const int*>(b);
    return (x > y) - (x < y);   // 回傳負/零/正
}

int data[] = {5, 2, 8, 1, 9, 3};
std::qsort(data, 6, sizeof(int), ascendingCmp);

14.2 std::sort(型別安全的現代替代)

C++ 的 std::sort 接受 lambda 或 functor,型別安全且通常更快(可 inline):

#include <algorithm>

std::sort(data, data + 6, [](int a, int b) { return a < b; });  // 升序
std::sort(data, data + 6, [](int a, int b) { return a > b; });  // 降序
比較 qsort(C) std::sort(C++)
比較器 函式指標 + void* 任意 callable(lambda / functor)
型別安全 否(需手動轉型)
效能 無法 inline 可 inline,通常更快
適用 C 互操作 C++ 首選

14.3 指向成員的指標(Pointer to Member)

成員函式有隱藏的 this,因此不能用一般函式指標儲存,需用指向成員的指標

struct Calculator {
    int value = 0;
    int add(int x)      { return value + x; }
    int multiply(int x) { return value * x; }
};

// 宣告:回傳型別 (類別::*名稱)(參數)
int (Calculator::*opPtr)(int) = &Calculator::add;

Calculator calc;  calc.value = 10;
(calc.*opPtr)(5);     // 透過物件呼叫 → 用 .*

Calculator* pc = &calc;
(pc->*opPtr)(5);      // 透過指標呼叫 → 用 ->*

std::mem_fn 可把成員函式包成一般可呼叫物件:

#include <functional>
auto fn = std::mem_fn(&Calculator::add);
fn(calc, 7);   // 等同 calc.add(7)

指向成員的指標常用於選單、狀態機、事件分派表


十五、RAII 與智慧指標(現代記憶體管理)

裸指標(new / delete)容易出錯:忘記 delete(記憶體洩漏)、delete 兩次(double free)、提前 return 或丟出例外導致 delete 未執行。智慧指標用 RAII 自動解決這些問題。

RAII(Resource Acquisition Is Initialization):把資源(記憶體、檔案、鎖)的生命週期綁定到物件的生命週期。物件離開作用域時,解構子自動釋放資源。

15.1 unique_ptr — 獨佔擁有權(首選)

#include <memory>

auto p = std::make_unique<Resource>("A");  // 優先用 make_unique,而非 new
p->use();

// 不可複製,但可移動(轉移擁有權)
auto q = std::move(p);   // 此後 p == nullptr

// 也能管理陣列
auto arr = std::make_unique<int[]>(5);
  • 零額外開銷,行為等同裸指標但安全
  • 離開作用域時自動 delete,不需手動釋放

15.2 shared_ptr — 共享擁有權(參考計數)

auto a = std::make_shared<Resource>("B");  // use_count = 1
{
    auto b = a;        // 複製 → use_count = 2
}                      // b 離開作用域 → use_count = 1
// 當最後一個 shared_ptr 銷毀時才釋放物件
  • 透過參考計數追蹤擁有者數量
  • 計數操作是 atomic,有少量開銷 → 熱路徑避免反覆複製

15.3 weak_ptr — 非擁有觀察者(打破循環)

兩個 shared_ptr 互相持有會形成循環參考,計數永遠歸不了零 → 記憶體洩漏。用 weak_ptr 觀察而不增加計數:

struct Node {
    std::shared_ptr<Node> next;   // 擁有
    std::weak_ptr<Node>   prev;   // 觀察(不擁有,打破循環)
};

if (auto p = node->prev.lock()) {  // lock() 安全取得,物件還在才成功
    p->use();
}

15.4 用參數型別表達擁有權語意(Core Guidelines)

你想表達 函式參數寫法
函式接管擁有權 以值傳 std::unique_ptr<T>
函式共享擁有權 以值傳 std::shared_ptr<T>
函式只是使用(不擁有) T*const T&
函式會重新指派智慧指標 unique_ptr<T>&

15.5 選擇指南

需求 建議
預設、獨佔擁有 unique_ptr(R.21:優先用 unique_ptr)
真的需要多方共享 shared_ptr
觀察但不擁有 / 打破循環 weak_ptr
建立物件 make_unique / make_shared(R.22/R.23)

智慧指標屬於 Ch16 的核心主題,本章先建立「擁有權」的觀念,理解為什麼現代 C++ 幾乎不再裸用 new / delete


十五點五、最佳實踐(Best Practices)

對應 C++ Core Guidelines,把以下原則內化,能避免絕大多數指標相關的 bug:

  1. F.60 / F.7:能用參考就別用指標。 當參數一定存在、不需重新指向時用 T&;只有「可能不存在(可選)」或「需要重新指向」才用 T*
  2. ES.47:用 nullptr,不要用 0NULL 型別明確、避免多載歧義。
  3. ES.65:先檢查再解參考。 解參考前確認指標非空(if (p) ...),尤其是來自外部的指標。
  4. ES.42:保持指標用法簡單直接。 指標算術只在陣列範圍內進行;越界即 UB。
  5. R.10 / R.11:避免裸 new / delete 動態記憶體交給智慧指標(make_unique / make_shared)以 RAII 自動管理。
  6. R.20 / R.21:預設用 unique_ptr,真的要共享才用 shared_ptr 表達擁有權;非擁有者用 T*T&
  7. I.11 / F.26:別用裸指標傳遞擁有權。 函式參數的裸指標代表「只是借用、不負責釋放」。
  8. Con 系列:const 正確性。 不修改指向值就用 const T*;用「const 在 * 左邊管值、右邊管指向」的口訣。
  9. 回傳值別回傳區域變數的指標 / 參考(懸垂);只回傳生命週期更長的物件。
  10. 參考不能重綁、不能為空——優先用它表達「保證有效的別名」,讓介面更安全。

一句話總結現代 C++ 的指標哲學:「擁有權交給智慧指標,借用就用參考或 const T*,裸 new/delete 幾乎絕跡。」


十六、常見陷阱

# 陷阱 說明 預防
1 野指標 未初始化的指標指向隨機位址 初始化為 nullptr 或有效位址
2 解參考空指標 程式 crash 先檢查 ptr != nullptr
3 懸垂指標 指向已釋放或已銷毀的記憶體 delete 後設 nullptr
4 懸垂參考 回傳區域變數的參考 只回傳存活期更長的物件
5 const 位置搞混 const int* vs int* const 記住口訣
6 指標宣告陷阱 int* a, b; 只有 a 是指標 一行一個宣告
7 指標越界 存取陣列外的記憶體 確保索引在 [0, size)
8 混淆 ref = b 以為是重新綁定 ref = b 是賦值,不是改綁
9 函式指標型別不匹配 參數或回傳型別不同 嚴格匹配簽名
10 記憶體洩漏 new 後忘記 delete 改用智慧指標(RAII)
11 double free 同一指標 delete 兩次 delete 後設 nullptr;用智慧指標
12 循環參考 兩個 shared_ptr 互相持有 其中一方改用 weak_ptr
13 修改字串字面量 透過 const char*"..." 要改就用 char[] 陣列
14 混淆 int(*p)[3] / int* p[3] 指向陣列 vs 指標陣列 * 是否被括號包住

懸垂指標範例

int* dangling() {
    int local = 42;
    return &local;  // local 在函式結束後銷毀 → 懸垂指標!
}

十七、重點整理

概念 要點
& 運算子 取得變數的記憶體位址
* 運算子 解參考,取得指標指向的值
-> 運算子 透過指標存取結構 / 類別成員
nullptr 空指標,永遠用它取代 NULL
const 指標 分清「指向 const」和「const 指標」
指標算術 每次移動 sizeof(型別) 個位元組
參考 別名、必須初始化、不能為空
函式指標 int (*fp)(int, int) = func;
callback 將函式指標傳入另一函式,延遲決定行為
std::function 現代 callback 包裝,支援 lambda
C 字串 '\0' 結尾的 char 陣列
二維陣列 連續記憶體,a[i][j] = *(*(a+i)+j)
指向成員 (obj.*p)(...)(ptr->*p)(...)
RAII 資源生命週期綁定物件生命週期
unique_ptr 獨佔擁有、零開銷、首選
shared_ptr 共享擁有、參考計數
weak_ptr 非擁有觀察、打破循環參考

十八、課堂活動

活動 1:記憶體位址觀察(10 分鐘)

執行 pointers.cpp,觀察: - 同一變數的值與位址 - 指標的值(指向的位址)vs 指標自身的位址 - 修改 *ptr 如何影響原始變數

活動 2:const 指標實驗(10 分鐘)

宣告四種 const 指標組合,逐一嘗試修改值和指向,記錄編譯器錯誤訊息。

活動 3:雙指標反轉(15 分鐘)

pointer_arithmetic.cpp 中的 reverseArray,手動 trace leftright 的移動過程。

活動 4:計算機 callback(15 分鐘)

修改 function_pointers.cpp,加入一個互動式計算機: - 提示使用者輸入運算符(+、-、*、/) - 用 selectOperation 選擇對應函式 - 印出結果

活動 5:指標 vs 參考辯論(5 分鐘)

分組討論:「設計一個 find() 函式,找不到時回傳什麼?」 - 指標方案:回傳 nullptr - 參考方案:無法表示「找不到」 - 引出後續 std::optional 等現代方案

活動 6:qsort vs std::sort(10 分鐘)

執行 advanced_pointers.cpp,比較 qsort(函式指標 + void*)與 std::sort(lambda)的寫法差異,討論型別安全與效能。

活動 7:觀察智慧指標的生命週期(15 分鐘)

執行 smart_pointers.cpp,從建構 / 解構訊息觀察: - unique_ptr 移動後原指標變為空 - shared_ptruse_count 隨複製 / 離開作用域增減 - weak_ptr 如何讓 Node 正確解構(將 prev 改回 shared_ptr 觀察洩漏)


十九、練習題

基礎

  1. 交換函式:分別用指標和參考寫 swap 函式,比較兩者的呼叫語法。
  2. const 練習:宣告四種 const 指標組合,嘗試修改並觀察編譯器錯誤訊息。
  3. nullptr 安全:寫函式接收 int*,若為 nullptr 則印出警告並安全返回。

進階

  1. 找最大值:寫函式接收整數陣列(用指標 + 大小),回傳最大值的指標。
  2. 反轉陣列:用指標算術實作陣列反轉(雙指標法)。
  3. 參考回傳:寫函式回傳 vector 中最大元素的參考,並透過參考修改它。
  4. 字串反轉:用兩個 char* 指標反轉 C 風格字串(不需 #include <algorithm>)。

函式指標

  1. 排序 callback:寫 sortDescending(int* arr, int size, bool (*compare)(int, int)),用函式指標控制升序 / 降序。
  2. 簡易選單:用函式指標陣列實作文字選單(1=加、2=減、3=乘、4=除),使用者選擇後執行對應運算。
  3. std::function 改寫:將第 8 題改用 std::function 和 lambda 實作,比較程式碼差異。

進階指標

  1. 回文判斷:用兩個 char* 指標判斷 C 風格字串是否為回文。
  2. 二維走訪:用 int (*p)[N] row 指標印出二維陣列的轉置(transpose)。
  3. qsort 結構排序:定義 struct Student { char name[20]; int score; },用 qsortscore 降序排序。
  4. 成員函式分派表:用指向成員函式的指標陣列實作一個小型計算機選單。

智慧指標

  1. unique_ptr 工廠:寫 std::unique_ptr<Shape> createShape(...) 工廠函式,回傳不同形狀並自動管理記憶體。
  2. shared 計數觀察:用 make_shared 建立物件,在不同作用域複製,印出每一步的 use_count
  3. 打破循環:寫一個雙向鏈結節點,先用兩個 shared_ptr 觀察記憶體洩漏,再把 prev 改成 weak_ptr 修正。

二十、課後測驗(口頭 / 選擇)

  1. int* pint& r 的主要差異是什麼?
  2. const int* pint* const p 有何不同?
  3. 為什麼 arr[i] 等價於 *(arr + i)
  4. 以下哪些會造成懸垂指標? - (a) 回傳區域變數的指標 - (b) delete 後繼續使用指標 - (c) 指標指向已 pop 的 vector 元素
  5. 函式指標 int (*fp)(int, int)* 代表什麼?
  6. 為什麼 capturing lambda 不能轉換為函式指標?
  7. C 風格字串如何判斷結尾?修改字串字面量會發生什麼事?
  8. int (*p)[3]int* p[3] 有何不同?
  9. 為什麼成員函式不能用一般函式指標儲存?要用什麼語法呼叫?
  10. unique_ptrshared_ptr 的擁有權語意差異是什麼?何時該用 weak_ptr
  11. 什麼是 RAII?智慧指標如何利用它避免記憶體洩漏?
  12. 為什麼應優先用 make_unique / make_shared 而非 new

對應範例程式

檔案 說明 建議時段
pointers.cpp 指標基礎:宣告、解參考、nullptr、const 指標 第 1–2 段
pointer_arithmetic.cpp 指標算術:遞增、陣列走訪、指標差、雙指標法 第 3 段
references.cpp 參考:宣告、別名、函式參數、const 參考 第 4 段
function_pointers.cpp 函式指標:callback、函式指標陣列、std::function 第 5 段
advanced_pointers.cpp 進階:C 字串、二維陣列、qsort/sort、指向成員 第 6 段
smart_pointers.cpp 智慧指標:RAII、unique/shared/weak_ptr、擁有權 第 7 段

編譯與執行

g++ -std=c++17 -Wall -o pointers pointers.cpp && ./pointers
g++ -std=c++17 -Wall -o pointer_arithmetic pointer_arithmetic.cpp && ./pointer_arithmetic
g++ -std=c++17 -Wall -o references references.cpp && ./references
g++ -std=c++17 -Wall -o function_pointers function_pointers.cpp && ./function_pointers
g++ -std=c++17 -Wall -o advanced_pointers advanced_pointers.cpp && ./advanced_pointers
g++ -std=c++17 -Wall -o smart_pointers smart_pointers.cpp && ./smart_pointers

延伸閱讀