11、const与指针:const修饰指针的三种形式、常量指针与指针常量、const指针的应用

const 和指针放在一起,很多人一开始会觉得绕。说实话,我当年学 C 语言的时候,也被这玩意儿搞晕过几次。但后来在项目中吃过亏,才真正重视起来。

const 修饰指针,说白了就三种情况。你把这三种情况理清了,后面写代码就能少踩很多坑。

11.1 const 修饰指针的三种形式

我们先看代码,再慢慢聊。

// 形式一:const 放在 * 左边
const int *p1;

// 形式二:const 放在 * 右边
int * const p2;

// 形式三:const 两边都有
const int * const p3;

这三种形式,分别对应什么含义?我一个个说。

11.1.1 形式一:const int *p(常量指针)

含义:指针指向的值不能改,但指针本身可以改。

也就是说,你不能通过 p 去修改它指向的那个 int 变量。但你可以让 p 指向别的地方。

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

// *p = 30;  // 错误!不能通过 p 修改 a 的值
p = &b;      // 正确!p 可以指向别处

我在项目中遇到过一种场景:某个函数要读取一个大型缓冲区,但绝对不能修改它。这时候我就会用 const int * 来传参。说白了,这就是一种「只读权限」的传递。

我的习惯:只要函数不需要修改指针指向的数据,我就加 const。这不仅是规范,更是自我保护。

11.1.2 形式二:int * const p(指针常量)

含义:指针本身不能改,但指向的值可以改。

你想想看,p 一旦初始化指向某个变量,就不能再指向别处了。但你可以通过 p 修改那个变量的值。

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

*p = 30;      // 正确!可以通过 p 修改 a 的值
// p = &b;   // 错误!p 不能指向别处

这种形式在嵌入式里很常见。比如你有一个固定的寄存器地址,你希望 p 永远指向那个地址,但寄存器的值是可以读写的。

我曾经踩过的坑:有一次我定义了一个指针常量,但忘了初始化。结果后面代码里试图修改它指向的值,程序直接崩溃。记住:指针常量必须在定义时初始化!

11.1.3 形式三:const int * const p(常指针常量)

含义:指针本身不能改,指向的值也不能改。

这就是双重锁定。p 只能指向初始化时那个变量,而且你不能通过 p 修改那个变量的值。

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

// *p = 30;  // 错误!不能修改指向的值
// p = &b;  // 错误!不能修改指针本身

这种形式用得相对少一些,但在一些关键配置参数、硬件描述表等场景下非常有用。嗯,这里要注意:它保证了指针和数据的双重安全。

11.2 常量指针 vs 指针常量

这两个概念很多人容易搞混。我教你一个记忆方法:

  • 常量指针(const int *):指针是个「常量」吗?不是。是它指向的东西是「常量」(不可修改)。所以叫「常量指针」其实不太准确,但大家都这么叫习惯了。
  • 指针常量(int * const):指针本身是「常量」,不能改。

说白了,你就看 const 修饰的是谁:

写法 名称 指针本身 指向的值
const int *p 常量指针 可改 不可改
int * const p 指针常量 不可改 可改
const int * const p 常指针常量 不可改 不可改

我建议你把这个表记下来。面试的时候经常考,写代码的时候也经常用。

11.3 const 指针的应用

光说不练假把式。我们看看实际项目中怎么用。

11.3.1 函数参数保护

这是最常见的用法。你写一个函数,不想让函数内部修改传入的数据,就用 const 指针。

void print_data(const int *data, int len) {
    for (int i = 0; i < len; i++) {
        printf("%d ", data[i]);
        // data[i] = 0;  // 如果写了这行,编译会报错
    }
}

我在项目中遇到过:有个同事写的函数,传进去一个数组,结果函数内部把数组内容改了。调用方完全不知情,查 bug 查了两天。从那以后,我要求团队里所有只读参数的函数,必须加 const。

11.3.2 硬件寄存器映射

嵌入式开发里,我们经常要访问固定的硬件地址。这时候指针常量就派上用场了。

// 假设 GPIOB 的基地址是 0x40010C00
#define GPIOB_BASE  ((GPIO_TypeDef *)0x40010C00)

// 更安全的做法:指针常量
GPIO_TypeDef * const GPIOB = (GPIO_TypeDef *)0x40010C00;

这样 GPIOB 永远指向那个地址,不会因为代码失误被改掉。

11.3.3 字符串常量

字符串字面量本身就是 const 的。所以用 const char * 来指向它,是最合理的。

const char *msg = "Hello, World!";
// msg[0] = 'h';  // 错误!字符串常量不可修改

你想想看,如果你用 char *msg = "Hello";,然后不小心改了它,程序可能崩溃。因为字符串常量通常存在只读存储区。

11.4 知识体系图

下面这张图帮你理清 const 与指针的完整关系:

const 与指针知识体系 const 修饰指针 const int *p 常量指针 int * const p 指针常量 const int * const p 常指针常量 特性 指针本身:可改 指向的值:不可改 应用:函数参数保护 特性 指针本身:不可改 指向的值:可改 应用:硬件寄存器映射 特性 指针本身:不可改 指向的值:不可改 应用:关键配置参数

11.5 避坑指南

最后,我分享几个实际开发中容易踩的坑:

  • const 位置搞混const int *pint const *p 是一样的,都是常量指针。但 int * const p 完全不同。我建议统一用 const int *p 的写法,读起来更自然。
  • 强制类型转换要小心:把 const int * 强制转成 int *,然后去修改值,这是未定义行为。编译器可能不会报错,但运行时可能出问题。
  • 函数返回值加 const:如果函数返回一个指针,而且你不希望调用方修改它指向的内容,返回值也要加 const。

核心总结:

  • const 在 * 左边 → 指向的值是常量(常量指针)
  • const 在 * 右边 → 指针本身是常量(指针常量)
  • const 两边都有 → 都是常量(常指针常量)
  • 能用 const 的地方尽量用,这是 C 语言里最便宜的安全保障

好了,const 与指针的内容就讲到这里。记住我一句话:const 不是束缚,是保护。保护你的数据不被意外修改,保护你的代码更健壮。

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