第9问:const关键字:const指针的三种形态与使用场景

const 这个关键字,说简单也简单,说复杂也复杂。

我见过不少写了三五年 C 语言的工程师,遇到 const 指针照样翻车。说白了,const 的位置不同,含义天差地别。今天咱们就把这玩意儿彻底掰扯清楚。

一、const 指针的三种形态

const 修饰指针,其实就三种组合。我习惯用一个口诀来记:「左定值,右定向,左右都定」

什么意思?看下面这张图就明白了。

const 指针三种形态 形态一:const int *p const 在 * 左边 指针指向的值不可改 指针本身可以改 *p = 5; // 错误 p = &b; // 正确 口诀:左定值 形态二:int * const p const 在 * 右边 指针本身不可改 指向的值可以改 *p = 5; // 正确 p = &b; // 错误 口诀:右定向 形态三:const int * const p const 在 * 两边 值和指针都不可改 *p = 5; // 错误 p = &b; // 错误 口诀:左右都定

二、逐一说清楚

形态一: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 *pint * const p 前者等价于 const int *p,后者是常量指针。别搞反了。
强制类型转换绕过 const int *p = (int *)const_ptr; 虽然能编译通过,但修改原 const 数据是未定义行为。
函数参数类型不匹配 int *const int * 参数 这个可以。反过来不行——不能把 const 指针传给非 const 参数。

五、我的建议

写代码时,养成一个习惯:能加 const 的地方,尽量加

这不是为了炫技。const 是编译器给你的免费保镖。你告诉它「这个值不许改」,它就会帮你盯着。一旦有人不小心想改,编译阶段就报错,而不是等到运行时崩溃。

我曾经在一个通信协议栈项目里,因为一个函数参数忘了加 const,导致底层缓冲区被意外修改,查了整整两天才定位到问题。从那以后,我写函数声明时第一件事就是问自己:这个参数需要改吗?不需要?好,加 const。

一句话总结:const 在 * 左边锁值,在 * 右边锁指针。记住这个,这辈子都不会再搞混。


公众号:蓝海资料掘金营,微信deep3321