首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

常量指針、指針常量、指向常量的指針常量 —— C語言指針宣告完全圖解

     編輯:關於C語言

前言

C 語言指針宣告中 `const` 的位置決定了「誰是常量」。搞混這三者是無數初學者(甚至資深開發者)的痛點。本文用「右左法則」、記憶體圖解、代碼實測、編譯器報錯示例,徹底釐清:

  • 常量指針 (Pointer to Constant) — `const int *p` / `int const *p`
  • 指針常量 (Constant Pointer) — `int * const p`
  • 指向常量的指針常量 (Constant Pointer to Constant) — `const int * const p`

一、核心口訣:讀聲音,鎖定「誰不可變」

從變數名向外閱讀,遇到 `const` 修飾誰,誰就不可變:

宣告讀法 (從變數名 p 向右再向左)p 值可變?*p 值可變?典型用途
const int *p
int const *p
p 是一個指針 → 指向 const int → 指向的 int 不可變✅ 是❌ 否函數參數傳入只讀緩衝區:
void print(const char *str);
int * const pp 是一個 const 指針 → 指向 int → 指針本身不可變❌ 否✅ 是固定指向特定硬體暫存器/記憶體映射 IO:
volatile uint32_t * const REG = (void*)0x40000000;
const int * const pp 是一個 const 指針 → 指向 const int → 雙重不可變❌ 否❌ 否只讀查找表、常量字串:
const char * const msg = "Hello";

二、記憶體與編譯器強制圖解

1. 常量指針 `const int *p`

int a = 10, b = 20;
const int *p = &a;  // p 指向 a
*p = 30;            // ❌ 編譯錯誤: assignment of read-only location '*p'
p = &b;             // ✅ 合法: 改變指向
printf("%d", *p);   // 20

記憶體圖:

p (可變指標)  ──┐
                ▼
           +--------+
           |  10    |  ← 唯讀區/棧區 (a)
           +--------+      ↑
           |  20    |  ← 唯讀區/棧區 (b)  p 可指向這裡
           +--------+

2. 指針常量 `int * const p`

int a = 10, b = 20;
int * const p = &a; // 初始化後不可改變指向
*p = 30;            // ✅ 合法: 修改指向的值
p = &b;             // ❌ 編譯錯誤: assignment of read-only variable 'p'
printf("%d", a);    // 30

記憶體圖:

p (唯讀指標)  ──┐
                ▼
           +--------+
           |  30    |  ← 可讀寫區 (a) 內容可改
           +--------+
           |  20    |  ← 另一變數 (b) p 永遠無法指向這裡
           +--------+

3. 指向常量的指針常量 `const int * const p`

int a = 10;
const int * const p = &a; // 綁定終身
*p = 20;  // ❌ 錯誤
p = NULL; // ❌ 錯誤
// 唯一合法操作: 讀取 *p

三、進階:多層指針與 const 組合

// 二級指針
int val = 100;
int *p1 = &val;
int * const p2 = p1;      // p2 是指針常量,指向 int
const int *p3 = p1;       // p3 是常量指針,指向 const int
const int * const *p4 = &p3; // p4 指向「常量指針」,p4 可變,*p4 (即 p3) 不可變,**p4 不可變

// 函數參數傳遞規則 (重要!)
void f1(int *p);          // 可接受 int*, 不可接受 const int* (丟棄 const)
void f2(const int *p);    // 可接受 int* 和 const int* (加上 const)
void f3(int * const p);   // 形參 p 為指針常量,僅影響函數內部 p 是否可改指向
void f4(const int * const p); // 形參雙重 const

四、實戰最佳實踐

  1. 函數參數默認加 `const`:除非函數需修改指向內容,否則參數宣告為 `const T *`。這既是文檔,又啟用編譯器保護,還允許傳入字串常量/只讀記憶體。
  2. 遍歷陣列/鏈表用指針常量:for (Node * const cur = head; cur; cur = cur->next) 防止誤寫 `cur = something_else`。
  3. 硬體暫存器映射:volatile uint32_t * const UART_DR = (void*)0x4000C000; — `volatile` 防優化,`const` 鎖定位址。
  4. 字串常量:const char * const GREETING = "Hello"; 雙重保障。
  5. 避免 `const` 轉型移除:`const_cast` (C++) 或強制轉型 (C) 移除 `const` 導致 UB,除非確知原物件非常量。

五、速查表:宣告 → 含義 → 編譯器保護

const int *p;        // 指向 const int 的指針      → 保護 *p
int * const p;       // 指向 int 的 const 指針      → 保護 p
const int * const p; // 指向 const int 的 const 指針 → 保護 p 和 *p

// 記憶口訣:const 修飾右邊的類型 (若右邊無類型則修飾左邊的 *)
// const int *p     → const 修飾 int   → *p 是 const
// int * const p    → const 修飾 *p    → p 是 const
// const int * const p → 兩個 const 分別修飾 int 和 *p

徹底搞懂這三者,不僅能避開編譯錯誤,更能在 API 設計中精確表達「所有權與可變性契約」,寫出更安全、自文檔化的 C 代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved