第9问:const关键字:const指针的三种形态与使用场景
const 这个关键字,说简单也简单,说复杂也复杂。
我见过不少写了三五年 C 语言的工程师,遇到 const 指针照样翻车。说白了,const 的位置不同,含义天差地别。今天咱们就把这玩意儿彻底掰扯清楚。
一、const 指针的三种形态
const 修饰指针,其实就三种组合。我习惯用一个口诀来记:「左定值,右定向,左右都定」。
什么意思?看下面这张图就明白了。
二、逐一说清楚
形态一:const int *p —— 指向常量的指针
这种写法,const 在 * 的左边。意思是:你不能通过 p 去修改它指向的那个值。
int a = 10;
int b = 20;
const int *p = &a;
// *p = 30; // 编译错误!不能通过 p 修改 a 的值
p = &b; // 没问题,指针本身可以变
我在项目中经常用这种形态。比如读取传感器数据,你只想让别人看,不想让别人改。这时候传一个 const int * 进去,编译器就帮你把门看住了。
核心理解:const 在 * 左边,锁定的是「值」。
形态二:int * const p —— 常量指针
const 在 * 的右边。意思是:指针本身是常量,初始化后不能再指向别处。
int a = 10;
int b = 20;
int * const p = &a;
*p = 30; // 没问题,可以通过 p 修改 a 的值
// p = &b; // 编译错误!指针本身不能改
这种形态我一般在硬件寄存器映射时用。比如某个外设的基地址是固定的,你希望代码里始终指向那个地址,但寄存器里的值可以读写。
注意:常量指针必须初始化!你不能先声明再赋值,编译器会报错。
形态三:const int * const p —— 双重锁定
两边都有 const。意思很明确:指针不能改,值也不能改。
int a = 10;
const int * const p = &a;
// *p = 30; // 错误!值不能改
// p = &b; // 错误!指针不能改
这种形态用得相对少一些。我一般在定义系统级常量表时用,比如一个只读的查找表,地址和内容都不允许任何人动。
三、使用场景与避坑指南
场景1:函数参数保护
这是最常见的用法。你写一个函数,不想让函数内部修改传入的数据。
// 好的做法
void print_data(const int *data, int len) {
for (int i = 0; i < len; i++) {
printf("%d ", data[i]);
// data[i] = 0; // 编译器会阻止你
}
}
我个人习惯:只要函数不需要修改传入的缓冲区,一律加 const。这不仅是规范,更是自文档化——看函数声明就知道它会不会改你的数据。
场景2:全局常量表
// 定义在 ROM 中的查找表
const int lookup_table[] = {1, 3, 5, 7, 9};
// 或者用指针形式
const int * const table_ptr = lookup_table;
在嵌入式系统里,这种写法可以把数据放到 Flash 而不是 RAM,省内存。
场景3:字符串常量
const char *msg = "Hello"; // 指向字符串字面量
// msg[0] = 'h'; // 未定义行为!字符串字面量不可修改
我曾经见过一个同事,用 char *msg = "Hello"; 然后试图修改字符串内容。在有些平台上能跑,换个编译器就崩了。用 const 声明,编译器直接帮你拦住这种危险操作。
小技巧:读 const 声明时,从右往左读。比如 const int * const p:p 是一个 const 指针,指向一个 const int。这样不容易搞混。
四、常见陷阱
| 陷阱 | 错误示例 | 说明 |
|---|---|---|
| const 位置搞混 | int const *p 和 int * const p |
前者等价于 const int *p,后者是常量指针。别搞反了。 |
| 强制类型转换绕过 const | int *p = (int *)const_ptr; |
虽然能编译通过,但修改原 const 数据是未定义行为。 |
| 函数参数类型不匹配 | 传 int * 给 const int * 参数 |
这个可以。反过来不行——不能把 const 指针传给非 const 参数。 |
五、我的建议
写代码时,养成一个习惯:能加 const 的地方,尽量加。
这不是为了炫技。const 是编译器给你的免费保镖。你告诉它「这个值不许改」,它就会帮你盯着。一旦有人不小心想改,编译阶段就报错,而不是等到运行时崩溃。
我曾经在一个通信协议栈项目里,因为一个函数参数忘了加 const,导致底层缓冲区被意外修改,查了整整两天才定位到问题。从那以后,我写函数声明时第一件事就是问自己:这个参数需要改吗?不需要?好,加 const。
一句话总结:const 在 * 左边锁值,在 * 右边锁指针。记住这个,这辈子都不会再搞混。
公众号:蓝海资料掘金营,微信deep3321