🔷 第七章 数组、指针和引用
📄 一句话概括

const 和 * 谁前谁后,锁的东西完全不同:const int *p 锁的是值(不能通过 p 改 a,但 p 能改指别人),叫"指向常量的指针";int * const p 锁的是指针(p 不能再指别人,但能改 a 的值),叫"指针常量";两个都写 const int * const p,两样都锁。口诀:const 贴着谁,就锁谁。

📝 本节和前面几节的关系

7.4.4 讲了第一根安全带(空指针)。这一节是第二根:用 const 限制指针的权限。它用的零件是 2.2.5 学过的 const(只读常量)。这一节的读法一旦掌握,后面 7.6 的函数参数(常写成 const char *s)和 7.8 的引用(const int &r)就都看懂了一半。

一、三兄弟排排站

假设有 int a = 10;,三种写法的权限完全不同:

const int *p1 = &a;          // ① 指向常量的指针
int * const p2 = &a;         // ② 指针常量
const int * const p3 = &a;   // ③ 两个都锁
写法 名字 能不能改 值(*p = 20) 能不能改 指针(p = &b)
const int *p1 指向常量的指针 ❌ 不能 ✅ 能
int * const p2 指针常量 ✅ 能 ❌ 不能
const int * const p3 两者都是常量 ❌ 不能 ❌ 不能

注意名字和权限是"反着"的,这是最容易记混的地方:

二、口诀:const 贴着谁,就锁谁

不用死记名字,看 const 挨着谁:

const int *p;      // const 贴着 int  → 锁"int"(值):*p 不能改
int const *p;      // 同上,完全等价
int * const p;     // const 贴着 p    → 锁"p"(指针):p 不能改
const int * const p;  // 两边都贴     → 两样都锁

一句话口诀:const 在 * 的左边 → 锁"值";在 * 的右边 → 锁"指针"。

记忆画面:* 像一堵墙,const 站哪边,哪边就被封住。

   const int * p          int * const p
   ┌─────┐  │  ┌─┐        ┌─┐  │  ┌─────────┐
   │const│  *  │p│        │ │  *  │  const  │
   └─────┘  │  └─┘        └─┘  │  └─────────┘
    锁"值"  ┘                   └─  锁"指针"

三、int const *p 和 const int *p 是一回事

const int *p1;      // ✅
int const *p2;      // ✅ 和上面完全等价

因为 const 修饰的是它左边最近的东西(如果左边没有,就看右边)——const int 和 int const 都是"常整数",所以两个声明意思一样。

但下面这个完全不是一回事:

const int *p;    // 锁值
int * const p;   // 锁指针

只有一个字的差别,含义天壤之别——这是本节最需要动手验证的地方。

四、动手 Demo:三种写法 + 现场报错

#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 20;

    // ① 指向常量的指针:锁值,指针能改
    const int *p1 = &a;
    cout << "*p1 = " << *p1 << endl;      // 10
    // *p1 = 99;                          // ❌ 编译错误:不能改指向的值
    p1 = &b;                              // ✅ 指针可以改指别人
    cout << "改指 b 后 *p1 = " << *p1 << endl;   // 20

    // ② 指针常量:锁指针,值能改
    int * const p2 = &a;
    *p2 = 99;                             // ✅ 能改值
    cout << "改值后 a = " << a << endl;   // 99
    // p2 = &b;                           // ❌ 编译错误:指针不能再指别人

    // ③ 两个都锁
    const int * const p3 = &b;
    // *p3 = 1;                           // ❌ 值不能改
    // p3 = &a;                           // ❌ 指针也不能改
    cout << "*p3 = " << *p3 << endl;      // 20
    return 0;
}

看到什么算成功:*p1 = 10、改指 b 后 *p1 = 20、改值后 a = 99、*p3 = 20。

一定要亲手做的事:把三条注释掉的语句依次解除注释,每次重新编译,看编译器报什么错:

解除注释 编译器会说什么 含义
*p1 = 99; assignment of read-only location '*p1' 值被锁
p2 = &b; assignment of read-only variable 'p2' 指针被锁

两条报错信息把本节的规则翻译成了大白话:read-only location 说的是"那块内存只读"(锁值);read-only variable 说的是"这个变量只读"(锁指针)。以后看到这两句话,就知道是哪种 const 拦住了你。

五、为什么需要它们

场景 用哪种 好处
函数只读传进来的字符串/数组,不改内容 const char *s 承诺"我不改你的数据",调用方放心,也能接收常量实参
把某个指针钉死(比如硬件寄存器地址) int * const p 防止程序中途把它指歪
常量表 + 固定位置 const int * const p 双重保险
给只读数据加一层访问 const int * 语法上禁止误写,编译器帮你把关

一句话:const 用在指针上,是把"不改"这件事写进类型里,让编译器替你把关——比在注释里写"请勿修改"可靠得多。

六、常见误区

  1. "const int *p 是 p 不能改"——错。是"p 指向的值不能改",p 自己能改指别人。想锁 p 要写 int * const p。
  2. "int * const p 是值不能改"——反了。是指针不能改,值能改。
  3. "int const *p 和 int * const p 一样"——完全不同。前者等价于 const int *p(锁值),后者锁指针。只差 const 的位置。
  4. "const int * const p 是多余"——不多余。它同时锁住两头,常用于"指向一个常量表且位置固定"的场景。
  5. "锁了值就没法改了"——通过这个指针没法改,但原变量还能直接改:a = 5; 依然合法(只要 a 自己不是 const)。const int *p 只是限制了"这条路"。
自测三问(点开对答案) 1. `const int *p` 和 `int * const p` 各锁什么?——前者锁**值**(`*p` 不可改),后者锁**指针**(`p` 不可改)。 2. `int const *p` 等价于哪一个?——`const int *p`(都锁值)。 3. 想让"指针和值都不能改",怎么写?——`const int * const p = &a;`

相关笔记