const 是 constant(常量)的缩写,作用是将变量声明为只读,告诉编译器”这个值不会被修改”。它既是给编译器看的约束,也是给读代码的人看的文档。

核心规则:看左边

const 修饰的是它左边的东西;如果左边没有东西,就修饰右边的。

const int a = 10;    // 左边没东西,修饰右边的 int → a 是常量
int const b = 20;    // 等价写法,修饰左边的 int → b 是常量

两种写法完全等价,但推荐用 const int,更符合直觉。

指针的四种组合

这是 const 最容易搞混的地方,记住”看左边”规则就不会错:

const int *p;        // const 修饰左边:const 在 int 左边没东西,修饰 int → *p 不可改,p 可改
int const *p;        // 等价于上面,const 修饰 int → *p 不可改,p 可改
int * const p;       // const 修饰左边的 *(指针)→ p 不可改,*p 可改
const int * const p; // 两个 const → *p 不可改,p 也不可改

用表格总结:

声明*p 能改吗?p 能改吗?含义
const int *p指向常量的指针(指针可以换方向)
int * const p常量指针(指针钉死在一个地址上)
const int * const p指向常量的常量指针

具体例子

int a = 1, b = 2;

// 1. 指向常量的指针
const int *p = &a;
*p = 10;   // ❌ 编译错误:不能通过 p 修改它指向的值
p = &b;    // ✅ 可以:p 本身可以指向别的变量
a = 10;    // ✅ 注意:a 本身不是 const,直接改 a 是可以的!
           //    const 只限制通过 p 去改,不限制变量本身

// 2. 常量指针
int * const q = &a;
*q = 10;   // ✅ 可以通过 q 修改值
q = &b;    // ❌ 编译错误:q 永远指向 a,不能换

// 3. 双重 const
const int * const r = &a;
*r = 10;   // ❌ 不能改值
r = &b;    // ❌ 不能换方向

重要理解

const int *p 不是说 p 指向的值真的永远不变,而是承诺”我不会通过 p 去改它”。变量本身可以通过其他方式(比如直接用变量名、通过其他非 const 指针)修改。

const 在函数参数中的用法

这是 const 最常用的场景之一。当你写一个函数,承诺不会修改传进来的数据,就用 const:

// 承诺:我只读取 buf 的内容,不会修改它
void printData(const char *buf) {
    // buf[0] = 'x';  // ❌ 编译器直接报错,防止你误改
    printf("%s\n", buf);  // ✅ 只读操作没问题
}

为什么要这么做?

  1. 编译器帮你抓 bug:不小心写了修改操作,编译期就报错
  2. 告诉调用者:放心传进来,我不会改你的数据
  3. 允许传入 const 数据:比如传入字符串字面量(字符串字面量本身就是 const 的)
// 如果参数不写 const,下面的调用会有警告甚至错误
printData("hello");  // "hello" 是 const char[6]

const vs define

两者都能定义”常量”,但本质不同:

const#define
本质带类型的只读变量预处理阶段的文本替换
类型检查✅ 有类型,编译器检查❌ 无类型,纯文本替换
作用域✅ 遵循 C 语言作用域❌ 从定义处到文件结束
调试✅ 可以在调试器中看到❌ 预处理后名字就消失了
内存通常占内存(编译器可能优化)不占变量内存,直接替换到代码
取地址✅ 可以 &p 取地址❌ 不能取地址
#define MAX 100      // 预处理时把所有 MAX 替换成 100
const int max = 100; // 一个只读的整型变量

int arr1[MAX];       // ✅ 可以(C99后VCC也支持,但数组大小推荐用宏或枚举)
int arr2[max];       // ⚠️ 在C中不一定可以(数组大小需要编译期常量,C++可以)

建议

需要编译期常量(数组大小、case 标签)用 #defineenum;需要带类型、有作用域的只读变量用 const

常见误区

误区一:const 变量存放在只读内存里

不一定。const 只是编译器层面的约束,通过强制类型转换可以绕过(但不推荐):

const int a = 10;
int *p = (int *)&a;
*p = 20;  // 能编译通过(有警告),但行为未定义
          // 在某些嵌入式平台上 const 数据确实在 Flash(只读),写会直接崩溃
          // 在 PC 上通常在栈或数据段,可以被改,但这是未定义行为

误区二:const 让代码变慢

不会。const 是编译期约束,运行时没有任何额外开销。编译器反而可能因为 const 做更好的优化。


下一步:02-缓冲区

相关:返回目录