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 * 来传参。说白了,这就是一种「只读权限」的传递。
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 与指针的完整关系:
11.5 避坑指南
最后,我分享几个实际开发中容易踩的坑:
- const 位置搞混:
const int *p和int const *p是一样的,都是常量指针。但int * const p完全不同。我建议统一用const int *p的写法,读起来更自然。 - 强制类型转换要小心:把
const int *强制转成int *,然后去修改值,这是未定义行为。编译器可能不会报错,但运行时可能出问题。 - 函数返回值加 const:如果函数返回一个指针,而且你不希望调用方修改它指向的内容,返回值也要加 const。
核心总结:
- const 在 * 左边 → 指向的值是常量(常量指针)
- const 在 * 右边 → 指针本身是常量(指针常量)
- const 两边都有 → 都是常量(常指针常量)
- 能用 const 的地方尽量用,这是 C 语言里最便宜的安全保障
好了,const 与指针的内容就讲到这里。记住我一句话:const 不是束缚,是保护。保护你的数据不被意外修改,保护你的代码更健壮。