第2题:const 关键字的各种用法——const int *p, int *const p, const int *const p 的区别
这道题,几乎是每个C语言面试官都会拿出来考一考的经典题目。说实话,我当年刚入行时也被它绕晕过。const 这个关键字看起来简单,但一旦和指针搅在一起,就很容易让人犯迷糊。
咱们今天就把这三种写法彻底掰扯清楚。你想想看,指针本身是一个变量,它存的是地址;而 const 修饰的,要么是指针指向的内容,要么是指针本身。搞明白这个,你就抓住了核心。
1. const int *p —— 指向常量的指针
这种写法,const 修饰的是 *p,也就是指针所指向的那个值。说白了:你不能通过 p 去修改它指向的那个变量,但 p 本身可以指向别处。
int a = 10;
int b = 20;
const int *p = &a;
*p = 30; // 错误!不能通过 p 修改 a 的值
p = &b; // 正确!p 可以指向别处
a = 30; // 正确!a 本身不是 const,可以直接改
我在项目中遇到过一种场景:某个函数要读取一个大型缓冲区,但绝对不能修改它。这时候我就会用 const int *p 作为参数类型,相当于给调用方一个承诺——"我只读,不改你数据"。
2. int *const p —— 常量指针
这次 const 修饰的是 p 本身。也就是说:p 这个指针变量一旦初始化,就不能再指向别处了。但它指向的那个值,你可以随便改。
int a = 10;
int b = 20;
int *const p = &a;
*p = 30; // 正确!可以修改 a 的值
p = &b; // 错误!p 是常量,不能重新指向
嗯,这里要注意:int *const p 必须在定义时初始化,否则后面就没机会了。我早期写代码时吃过这个亏——声明了一个常量指针忘了赋初值,编译直接报错,当时还纳闷了半天。
3. const int *const p —— 指向常量的常量指针
这个就是前两种的组合。const 既修饰了 *p,又修饰了 p。结果就是:p 不能指向别处,也不能通过 p 修改它指向的值。双重锁定,动弹不得。
int a = 10;
int b = 20;
const int *const p = &a;
*p = 30; // 错误!不能通过 p 修改 a
p = &b; // 错误!p 本身也不能改
a = 30; // 正确!a 本身不是 const
这种写法在嵌入式开发中很常见。比如你要定义一个硬件寄存器的地址,既不想让指针乱跑,又不想让程序误写那个地址的值。我曾经在写STM32的驱动时,就用 const uint32_t *const 来定义只读寄存器的基地址,安全又清晰。
4. 三种用法的对比总结
| 声明 | 能否修改 *p | 能否修改 p | 必须初始化 |
|---|---|---|---|
const int *p |
否 | 是 | 否 |
int *const p |
是 | 否 | 是 |
const int *const p |
否 | 否 | 是 |
我个人习惯用一个口诀来记:"const 在 * 左边,修饰的是指向的内容;const 在 * 右边,修饰的是指针本身"。你试试看,是不是一下子就清晰了?
5. 避坑指南
我曾经在代码审查时看到有人这样写:
int const *p; // 这等价于 const int *p
没错,int const *p 和 const int *p 是完全一样的。const 只要出现在 * 的左边,不管是在 int 前面还是后面,效果都一样。但为了可读性,我建议统一写成 const int *p,这样更符合从左到右的阅读习惯。
还有一个坑:如果你把 const 用在函数参数里,记得要配合好。比如:
void foo(const int *p) {
// 这里只能读,不能写
}
调用方传一个非 const 的指针进来是没问题的,但反过来就不行——你不能把 const int * 传给一个期望 int * 的函数。编译器会警告,甚至报错。这其实是 C 语言的一种类型安全机制,别想着绕过去。
6. 知识体系结构图
下面这张图帮你把三种用法的关系理清楚:
说白了,const 的用法就是一层窗户纸。捅破了,你会发现它其实很简单。我个人建议你在写代码时,多主动用 const 来约束指针的行为——这不仅能减少 bug,还能让读你代码的人一眼就明白你的意图。
公众号:蓝海资料掘金营,微信deep3321