什麼是指針?
指針** 是一個變量,其值是**另一個變量的內存地址**。它不直接存儲數據,而是「指向」數據所在的位置。這是 C/C++ 手動內存管理、高效數據結構、系統編程的基石。
基礎語法與核心操作
| 操作 | 符號 | 語義 | 範例 |
| 聲明 | `*` | 類型修飾符,`int* p` = 指向 int 的指針 | `int* p;` |
| 取地址 | `&` | 獲取變量內存地址 | `p = #` |
| 解引用 | `*` | 訪問指針指向的值 | `*p = 20;` |
| 指針算術 | `++` `--` `+` `-` | 按類型大小移動 (指向下一個/上一個元素) | `p++` 移動 `sizeof(int)` 字節 |
最小化範例
#include
int main() {
int num = 10;
int* p = # // p 存儲 num 的地址
printf("num 值: %d\n", num); // 10
printf("p 地址: %p\n", (void*)p); // 如 0x7ffe...
printf("*p 值: %d\n", *p); // 10 (解引用)
*p = 25; // 通過指針修改 num
printf("num 新值: %d\n", num); // 25
return 0;
}
經典陷阱:返回局部變量地址
// ❌ 錯誤:棧上局部變量函數結束即銷毀
int* BadFunction() {
int local = 42;
return &local; // 返回已釋放棧內存地址 → 未定義行為
}
// ✅ 正確:動態分配 (堆內存) 或靜態/全局變量
int* GoodFunction() {
int* p = (int*)malloc(sizeof(int)); // C 風格
*p = 42;
return p; // 調用者負責 free()
}
// C++ 現代風格:智能指針自動管理
#include
std::unique_ptr ModernFunction() {
return std::make_unique(42); // 自動釋放,無內存洩漏
}
動態內存分配
| 語言 | 分配 | 釋放 | 備註 |
| C | `malloc` / `calloc` / `realloc` | `free` | 不調用構造/析構,**不適用於 C++ 類對象** |
| C++ | `new Type` / `new Type[N]` | `delete` / `delete[]` | 調用構造函數/析構函數,**必須配對使用** |
| C++11+ | `std::make_unique` / `std::make_shared` | **自動** (RAII) | **首選**,零裸指針,異常安全 |
數組分配與釋放
// C
int* arr = (int*)malloc(10 * sizeof(int));
free(arr);
// C++
int* arr = new int[10];
delete[] arr; // 必須用 delete[],否則僅析構第一個元素 → 內存洩漏/UB
// 現代 C++:容器替代裸數組
std::vector vec(10); // 自動管理,擴容、邊界檢查、RAII
std::array arr; // 固定大小,棧分配,零開銷
指針作為函數參數:傳遞地址實現「引用語義」
// C 風格:修改調用者變量
void AddFive(int* n) {
*n += 5; // 解引用修改
}
int main() {
int val = 10;
AddFive(&val); // 傳遞地址
printf("%d", val); // 15
}
// C++ 引用參數 (語法糖,本質仍是指針)
void AddFiveRef(int& n) { n += 5; }
AddFiveRef(val); // 無需 &,調用處更清晰
// 現代 C++:傳值 + 返回值 (函數式風格) 或 span/view
int AddFivePure(int n) { return n + 5; }
std::span Process(std::span data) { ... } // 非擁有視圖
指向類/結構的指針:成員訪問運算符 `->`
struct Node { int value; Node* next; };
Node* head = new Node{1, nullptr};
head->value = 10; // 等價於 (*head).value
head->next = new Node{2, nullptr};
// 現代 C++:智能指針鏈表
struct SmartNode {
int value;
std::unique_ptr next; // 擁有權語義清晰
};
auto head = std::make_unique(SmartNode{1, nullptr});
head->next = std::make_unique(SmartNode{2, nullptr});
// 無需手動 delete,離開作用域自動遞歸釋放
指向數組的指針與指針算術
int arr[3] = {10, 20, 30};
int* p = arr; // 數組名退化為首元素地址
// 等價:int* p = &arr[0];
printf("%d\n", *p); // 10
p++; // 移動到下一個 int
printf("%d\n", *p); // 20
printf("%d\n", *(p+1)); // 30 (不修改 p)
// 關鍵:編譯器不檢查越界,程序員自負
// 現代替代:std::span view(arr, 3); // 帶邊界信息的視圖
多級指針與指針數組
// 指針的指針:修改指針本身 (如鏈表頭插入)
void InsertHead(Node** head, int val) {
Node* newNode = new Node{val, *head};
*head = newNode; // 修改調用者的 head 指針
}
// 指針數組:字符串數組、argv
char* names[] = {"Alice", "Bob", "Charlie"};
// 等價:char** names = ... (指向指針的指針)
const 與指針:四種組合語義
| 聲明 | 含義 | 可修改指針? | 可修改值? |
| `int* p` | 指向 int 的指針 | ✅ | ✅ |
| `const int* p` / `int const* p` | 指向**常量 int** 的指針 | ✅ | ❌ |
| `int* const p` | **常量指針** 指向 int | ❌ | ✅ |
| `const int* const p` | 常量指針指向常量 int | ❌ | ❌ |
口訣:**`const` 在 `*` 左 → 值不可變;`const` 在 `*` 右 → 指針不可變。**
現代 C++:智能指針與 RAII — 徹底告別裸指針
#include
// 獨占所有權:不可複製,可移動
std::unique_ptr p1 = std::make_unique();
auto p2 = std::move(p1); // 所有權轉移,p1 變空
// 共享所有權:引用計數,線程安全控制塊
std::shared_ptr sp1 = std::make_shared();
std::shared_ptr sp2 = sp1; // 計數+1
// 離開作用域計數-1,歸零時自動 delete
// 弱引用:打破循環引用
std::weak_ptr wp = sp1;
if (auto locked = wp.lock()) { /* 安全訪問 */ }
// 自定義刪除器:管理非 new 分配資源 (FILE*, socket, GPU buffer)
auto file = std::unique_ptr(fopen("a.txt","w"), fclose);
常見錯誤與調試技巧
| 錯誤 | 現象 | 排查工具 |
| 野指針 (未初始化) | 隨機崩潰/數據損壞 | 編譯器警告 `-Wall`、AddressSanitizer (`-fsanitize=address`) |
| 懸空指針 (use-after-free) | 訪問已釋放內存 | ASan、Valgrind、智能指針消除 |
| 內存洩漏 (忘記 delete) | 內存單調增長、OOM | Valgrind、Visual Studio 診斷工具、智能指針 |
| 雙重釋放 | 堆損壞、崩潰 | ASan、所有權語義清晰化 |
| 緩衝區溢出 | 覆蓋相鄰內存、安全漏洞 | ASan、MSVC `/GS`、std::span/容器 |
C 與 C++ 關鍵差異總結
- C:`malloc`/`free`、無構造析構、手動管理、易出錯。
- C++:`new`/`delete` 調用構造/析構、**RAII (資源獲取即初始化)**、**智能指針 (`unique_ptr`/`shared_ptr`) 是現代標準**、容器/算法/視圖替代裸指針算術。
- **原則**:現代 C++ 代碼中 **99% 場景不應出現裸指針 (`T*`)**,僅在與 C API 交互、非擁有觀察者 (`T*`/`std::span`/`std::string_view`)、性能極致熱路徑時使用。
結語
指針是 C/C++ 操控內存的核心工具,理解**地址、解引用、所有權、生命週期**是寫出正確高效代碼的前提。**現代 C++ 以 RAII 與智能指針將內存管理自動化、異常安全化、所有權語義化**,應作為首選範式。掌握裸指針機制是為了讀懂舊代碼、調試底層問題、與 C 互操作,而非日常開發工具。