33、指针进阶5:指针与常量
常量指针和指针常量的中文名字很像。与其背名字,不如分别回答:指针能不能改指向?能不能通过它修改对象?
const 约束哪一层
本文以 C17 为基线,下面三种写法的基本权限也适用于 C++17:
| 声明 | 可改指向 | 可经它改对象 |
|---|---|---|
const int* p | 是 | 否 |
int* const p | 否 | 是,前提是对象本身可修改 |
const int* const p | 否 | 否 |
读声明时,从 p 开始看:离指针星号右侧的 const 限制指针对象本身;const int 则限制经该指针访问 int 的权限。
常量指针:不能经此路径写入
const int* p 与 int const* p 等价。p 可以重新指向另一个合适对象,但不能写 *p = ...。

这不等于所指对象必定是一个永远不能修改的对象。若 a 本来是普通 int,只是通过 const int* 读取 a,仍可以用原名字 a 修改它。const 在此约束的是这一访问路径。
指针常量:地址固定,对象可以改
int* const p 必须初始化,之后不能给 p 赋另一个地址。如果所指对象是可修改的 int,仍然能用 *p 修改它。

两处都加 const,则两个限制同时生效:不能改 p 的指向,也不能通过 p 写入对象。

即使是 const int* const,也不要自动推断原对象本身不可变。图中 a 是普通 int,其他合法可写路径仍可以修改 a;如果对象本身定义为 const,则试图强制去掉 const 后写入属于另一回事。
C17 可运行示例
#include <assert.h>
#include <stdio.h>
int main(void) {
int a = 10;
int b = 20;
const int* readable = &a;
readable = &b;
a = 11;
assert(*readable == 20);
int* const fixed = &a;
*fixed = 12;
assert(a == 12);
const int* const fixed_readable = &a;
a = 13;
assert(*fixed_readable == 13);
printf("%d %d\n", a, b);
return 0;
}
输出 13 20。三个指针展示的是不同权限,没有对真正的 const 对象做非法写入。
这些操作应当编译失败
以下是故意的非法示例,单独保存为 C 文件编译应产生诊断,不与上面的可运行程序混用:
int main(void) {
int a = 1;
int b = 2;
const int* p = &a;
*p = 3;
int* const q = &a;
q = &b;
return 0;
}
第一处违反经 p 的只读访问权限,第二处试图给 const 指针对象 q 重新赋值。编译器提示措辞因版本不同而不同,判断依据是约束而不是某条英文报错的固定全文。
练习与依据
把三种声明分别放到函数参数与局部变量中,说明它们约束的是调用者对象、形参本身还是访问路径。不要将“参数内部不能重绑指针”理解为“调用者指针永远不能改变”。
示例按 C17 编译,标准版本见 WG14 项目列表;类型限定符的基础条款可对照公开的 C11 草案 N1570,注意版本区别。本章不涉及多级指针的 const 转换,不能把一级指针结论直接推广到 int**。
