一句话概括
const 是 C 语言中的一个类型限定符(Type Qualifier),它用来告诉编译器:“请把一个变量标记为只读的,它的值在初始化后就不能再被修改了。”

const 就是用来创建常量的。
const 的核心作用与好处
使用 const 主要有以下三个核心好处:
- 提高代码安全性:它能防止代码中意外地修改不应该被修改的变量,减少程序运行时的 bug。
- 增加代码可读性:当其他程序员(或者未来的你)看到
const时,就能立刻明白这个变量是一个常量,不应该被赋值,从而理解代码的意图。 - 帮助编译器优化:编译器知道一个
const变量的值永远不会改变,因此它可以进行一些更激进的优化,有时甚至能生成更高效的机器码。
const 的基本用法
const 的语法规则有点灵活,但核心思想不变。const 关键字可以放在类型的前面或后面,效果是一样的,把 const 放在变量名前面是更现代、更推荐的风格。
修饰普通变量(最常见)
这是最简单的用法,创建一个只读的变量。

// 推荐写法 (const 在变量名前) const int MAX_AGE = 100; // 也可以这样写 (const 在类型后,效果相同) // int const MAX_AGE = 100; // 下面的代码会编译错误! // MAX_AGE = 101; // error: assignment of read-only variable 'MAX_AGE'
这里,MAX_AGE 被定义为一个常量,它的值是 100,任何试图修改 MAX_AGE 的操作都会被编译器报错。
修饰指针(这是最容易混淆的地方)
当 const 和指针 结合时,情况就变得复杂了。const 的位置决定了“什么”是不可变的。
我们分两种情况来看:
情况 A:const 修饰指针指向的数据(内容不可变)
语法:const int * p; 或者 int const * p;

解读:const 紧挨着 int,说明 int(也就是 p 指向的那个整数)是只读的,指针 p 本身是可以指向别的地方的。
int a = 10; int b = 20; const int * p = &a; // p 是一个指向 "只读整数" 的指针 // *p = 30; // 错误!不能通过 p 修改它指向的数据,因为数据是只读的 p = &b; // 正确!可以改变 p 指向,让它指向 b
情况 B:const 修饰指针本身(指针地址不可变)
语法:int * const p;
解读:const 紧挨着 ,说明指针 p 本身是只读的,你不能让 p 指向新的地址,但是你可以修改 p 指向的那个数据。
int a = 10; int b = 20; int * const p = &a; // p 是一个 "只读指针",它必须一直指向 a *p = 30; // 正确!可以修改 p 指向的数据 a 的值 // p = &b; // 错误!不能改变 p 指向,p 是一个只读指针
情况 C:两者都不可变(最严格的指针)
语法:const int * const p;
解读:一个 const 修饰数据,另一个 const 修饰指针,这意味着指针本身和它指向的数据都是只读的。
int a = 10; const int * const p = &a; // *p = 30; // 错误!数据不可变 // p = &b; // 错误!指针本身也不可变
修饰函数参数
将函数参数声明为 const 是一个非常好的编程习惯,它向调用者承诺:“我不会在这个函数内部修改这个参数的值。”
// 一个打印数组的函数,承诺不会修改数组内容
void print_array(const int arr[], int size) {
for (int i = 0; i < size; i++) {
// arr[i] = 0; // 错误!不能修改 const 数组
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int my_numbers[] = {1, 2, 3, 4, 5};
print_array(my_numbers, 5);
return 0;
}
修饰函数返回值
当函数返回一个指针时,如果返回的数据不应该被调用者修改,就可以用 const 来修饰返回类型。
const int * get_max_value() {
static int max_value = 1000; // 使用 static 确保函数返回后变量还存在
return &max_value;
}
int main() {
const int * p_max = get_max_value();
printf("Max value is: %d\n", *p_max);
// *p_max = 2000; // 错误!不能通过返回的指针修改数据
return 0;
}
const 和 #define 的区别
很多初学者会混淆 const 和 C 语言的宏定义 #define,它们虽然都能创建常量,但有本质区别:
| 特性 | const |
#define |
|---|---|---|
| 类型 | 有类型,编译器知道它的类型,可以进行类型检查。 | 无类型,只是简单的文本替换,编译器不知道它的类型。 |
| 作用域 | 有作用域,遵循 C 语言的变量作用域规则(只在 内有效)。 | 无作用域,从定义到文件结尾都有效,除非用 #undef。 |
| 处理时机 | 编译时,编译器会处理它,会进行类型检查和内存分配(。 | 预处理时,在编译之前,预处理器会直接进行文本替换。 |
| 调试 | 可以调试,在调试时能看到 const 变量的名字和值。 |
无法调试,预处理器替换后,名字就消失了,调试时只能看到替换后的值。 |
| 定义方式 | 定义变量,可以指定类型。 | 定义宏,不指定类型。 |
在 C 语言中,优先使用 const,除非你需要做一些 const 做不到的事情(比如定义宏函数,或者定义一个全局的、无类型的常量)。
一个特殊的陷阱:const 全局变量的“内部链接”
这是一个在 C 语言中非常重要的知识点。
在 C 语言中,一个全局 const 变量(没有 static)默认具有内部链接,这意味着它只在定义它的那个源文件(.c 文件)中可见。
这与普通的全局变量(默认外部链接)形成鲜明对比。
示例:
const_var.c
#include <stdio.h> // 这是一个全局常量,具有内部链接 const int GLOBAL_CONST = 100;
main.c
#include <stdio.h>
// 尝试从另一个文件访问
extern int GLOBAL_CONST; // extern 声明
int main() {
printf("Global const value: %d\n", GLOBAL_CONST); // 编译错误!链接器找不到 GLOBAL_CONST
return 0;
}
编译这段代码时,main.c 中的 extern 声明会告诉编译器:“GLOBAL_CONST 这个变量应该在其他地方定义”,由于 const_var.c 中的 GLOBAL_CONST 具有内部链接,main.c 的链接器根本“看不见”它,从而导致链接错误。
如何解决?
如果你想让一个全局 const 变量像普通全局变量一样被其他文件访问,你需要显式地使用 extern 关键字。
const_var.c (修改后)
#include <stdio.h> // 显式声明为外部链接 extern const int GLOBAL_CONST; const int GLOBAL_CONST = 100;
或者,在声明时加上 extern:
// 在头文件中声明 extern const int GLOBAL_CONST; // 在 .c 文件中定义 const int GLOBAL_CONST = 100;
这样,GLOBAL_CONST 就可以被其他文件通过 extern 正确访问了。
注意:在 C++ 中,这个规则被改变了,C++ 中的全局
const变量默认具有外部链接,行为和普通全局变量一致,这也是 C 和 C++ 在兼容性上的一个常见陷阱。
const是创建只读变量(常量)的关键字。- 核心好处是安全、可读、可优化。
const修饰指针时,位置决定了“什么”不可变:const int * p不可变),int * const p(指针本身不可变)。- 优先使用
const而不是#define。 - C 语言中,全局
const变量默认是内部链接,不能被其他文件直接访问,这一点需要特别注意。
