第1章:const与指针——三种组合,三种人生

指针这东西,本来就已经够让人头疼了。再加上一个const,很多人直接就懵了。我记得刚学C语言那会儿,看到const int *pint * const p,心里就一个想法:这俩到底有啥区别?

后来在嵌入式项目里栽过跟头,才真正搞明白。今天咱们就把这三种组合掰开揉碎了讲清楚。

1.1 先搞清楚const修饰的是谁

其实核心就一句话:const修饰的是它右边最近的那个东西。你想想看,是不是这个理?

咱们来看三个典型写法:

const int *p;      // 情况A:指向const整型的指针
int * const p;     // 情况B:指向整型的const指针
const int * const p; // 情况C:指向const整型的const指针

嗯,这里要注意:读法是有技巧的。从右往左读,反而更清楚。

快速记忆口诀:

  • const int *p → p是一个指针,指向一个const int(指针可变,指向的值不可变)
  • int * const p → p是一个const指针,指向一个int(指针不可变,指向的值可变)
  • const int * const p → p是一个const指针,指向一个const int(指针和值都不可变)

1.2 情况A:const int *p —— 指向常量的指针

这种写法,说白了就是:你可以改变指针的指向,但不能通过指针修改指向的值

int a = 10;
int b = 20;
const int *p = &a;

*p = 30;  // ❌ 编译错误!不能修改指向的值
p = &b;   // ✅ 可以!指针可以指向别处

我在项目中遇到过这种情况:读取传感器数据时,有些寄存器是只读的。用const int *来指向它们,编译器会帮你拦住所有写操作。这比你自己记着“别写啊别写”靠谱多了。

实用场景:

  • 函数参数中,如果你只想读取数据而不修改它,用const int *传参
  • 指向硬件只读寄存器
  • 字符串常量(const char *str = "hello";

1.3 情况B:int * const p —— 常量指针

这种正好反过来:指针本身是常量,不能指向别处,但可以通过指针修改指向的值

int a = 10;
int b = 20;
int * const p = &a;

*p = 30;  // ✅ 可以!修改指向的值
p = &b;   // ❌ 编译错误!指针不能指向别处

我曾经在写DMA(直接存储器访问)驱动时用过这个。DMA控制器的寄存器地址是固定的,用int * const来定义,既保证了地址不会跑偏,又能正常读写寄存器值。嗯,这种场景下特别实用。

注意:常量指针必须在定义时初始化,因为之后不能再赋值了。

int * const p;  // ❌ 编译错误!未初始化
int * const p = &a;  // ✅ 正确

1.4 情况C:const int * const p —— 双重const

这个就厉害了——指针不能改,指向的值也不能改。两头都锁死。

int a = 10;
int b = 20;
const int * const p = &a;

*p = 30;  // ❌ 编译错误!不能修改值
p = &b;   // ❌ 编译错误!不能修改指针

说实话,这种写法在实际项目中用得不多。但我在做Bootloader时用过一次——指向一个固定的、只读的系统配置表。那个表在Flash里,地址固定,内容也不能改。用const int * const正好。

1.5 三种组合对比表

声明 指针本身 指向的值 典型场景
const int *p 可变 不可变 函数参数、只读数据
int * const p 不可变 可变 硬件寄存器地址
const int * const p 不可变 不可变 系统配置表、Bootloader

1.6 避坑指南

我曾经在代码里写过这样的bug:

const int *p;
int a = 5;
p = &a;
// 后来忘了p是const的,试图 *p = 10;
// 编译器报错,排查了半天才想起来

还有一次更隐蔽:

void func(const int *p) {
    int *q = p;  // ❌ 编译警告!丢弃了const限定符
    *q = 100;    // 危险!
}

嗯,这里要注意:const指针不能隐式转换为非const指针。如果你非要转,得用强制类型转换,但那样做通常意味着你的设计有问题。

我的建议:

  • 能用const就用const,让编译器帮你检查
  • 函数参数中,如果只读不写,加const
  • 全局指针变量,如果指向固定地址,用int * const
  • 别轻易去掉const,那往往是bug的源头

1.7 知识体系图

下面这张图帮你理清这三种组合的关系:

const与指针的三种组合 const int *p 指向常量的指针 int * const p 常量指针 const int * const p 双重const 特性 ✅ 指针可变 ❌ 值不可变 🔒 只读访问 特性 ❌ 指针不可变 ✅ 值可变 🔒 固定地址 特性 ❌ 指针不可变 ❌ 值不可变 🔒 双重锁定 核心记忆法:从右往左读 const修饰它右边最近的那个东西 const int *p → p是(指向const int的)指针

1.8 写在最后

这三种组合,说白了就是谁不能被修改的问题。指针本身?还是指向的值?还是两者都不能改?搞清楚这个,你就掌握了const与指针的精髓。

我个人习惯是:能加const的地方都加上。这不仅仅是风格问题,更是让编译器帮你抓bug。你想想看,如果某个变量本不该被修改,结果不小心改了,那bug多难查啊。const就是你的第一道防线。

下一章咱们聊聊指针运算,那又是另一个有意思的话题了。


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