C 的 const 与 C++ 的 const/constexpr
const 和 constexpr 都是很常见的关键字,但实际使用时经常会把“只读语义”和“编译期常量”混在一起。把这几个概念放在同一篇里梳理清楚。
C 里的 const
C 里的 const 本质上是一个类型限定符,表示不能通过这个表达式去修改对象。
先看最基本的变量场景:
1 | const int n = 10; |
这里的重点不是“对象绝对不可变”,而是编译器不允许你再通过 n 这个表达式给它赋值。
1 | const int n = 10; |
指针里的 const
const 一旦和指针放在一起,就容易混。最稳妥的判断方式是直接看它修饰谁。
1 | const int *p1 = 0; |
它们分别表示:
const int *p1:p1指向的内容不能通过p1修改,但p1可以改指向int *const p2:p2本身不能改指向,但可以通过p2修改它指向的内容const int *const p3:指向不能改,内容也不能通过它改
实践里可以先按下面这个规则判断:
const在*左边,通常限制“指向的内容”const靠近变量名时,通常限制“指针本身”
举个例子:
1 | int a = 1; |
C 里 const 的一个常见误区
一个很常见的误解,是把 const 和“编译期常量”直接画等号。
在 C 里,const 更偏向“只读语义”,不等于它一定能拿去做所有要求编译期常量的场景。也就是说,const 的重点是“限制修改”,不一定是“在预处理或编译早期就能完全替换成字面量”。
所以在 C 语言里,先把 const 理解成“只读限制”,通常是最准确的切入点。
C++ 里的 const
C++ 继承了 C 里 const 的基本含义,但因为 C++ 引入了引用、类和成员函数,const 的使用范围也明显更广。
const 变量
最基本的写法和 C 类似:
1 | const int n = 10; |
语义和 C 中一致:不能通过这个名字修改对象。
const 引用
在 C++ 里,const 引用最常见的落点就是函数参数:
1 | void print(const std::string& s) { |
这种写法通常有两个直接收益:
- 避免不必要的对象拷贝
- 同时明确函数不会修改参数
因此在“只读的大对象传参”这个场景里,const T& 基本是默认选项。
const 成员函数
类的成员函数后面也可以加 const:
1 | struct A { |
这里的 const 表示这个成员函数不会修改当前对象的普通成员。
也正因为如此,const 对象只能调用 const 成员函数:
1 | const A a{1}; |
这也是类接口设计里经常要区分“观察操作”和“修改操作”的原因。
constexpr 是什么
constexpr 是 C++ 提供的关键字,C 语言里没有对应概念。
可以先把它概括成一句话:constexpr 用来描述“这个值或这个函数结果可以在编译期确定”。
最常见的是 constexpr 变量:
1 | constexpr int buf_size = 1024; |
它比普通 const 更强,因为它强调的是“编译期常量表达式”。
constexpr 也可以修饰函数:
1 | constexpr int square(int x) { |
如果传入的是编译期可知的值,那么结果也可以在编译期算出来:
1 | constexpr int n = square(8); |
需要注意的是,constexpr 函数不等于“只能在编译期调用”。它也可以参与运行期计算,只是在条件满足时,编译器可以提前完成求值。
const 和 constexpr 的区别
这两个概念很容易混在一起,但侧重点其实很明确:
const强调“只读,不允许修改”constexpr强调“编译期可求值”
可以直接记成:
const解决的是“能不能改”constexpr解决的是“能不能在编译期确定”
例如:
1 | const int a = 10; |
这两个看起来都像常量,但语义并不相同。b 明确是编译期常量;a 只是只读对象,在很多场景下也能作为常量使用,但它的核心语义仍然是“不可修改”。
所以如果要表达“这是一个用于编译期求值的常量”,优先考虑 constexpr;如果只是表达“这个对象创建后不应再被修改”,const 就足够了。
constexpr、const 和 #define 的区别
这三个经常都被拿来“定义常量”,但它们其实不在同一个层面上。
先看一个对比表:
| 方式 | 所处阶段 | 有无类型 | 有无作用域 | 主要语义 | 更适合的场景 | 主要问题 |
|---|---|---|---|---|---|---|
#define BUF_SIZE 1024 |
预处理阶段 | 无 | 无 | 文本替换 | 条件编译、头文件保护、宏开关 | 没有类型,调试体验差,容易产生意外替换 |
const int buf_size = 1024; |
语言语义层面 | 有 | 有 | 只读对象 | 表达“这个值不应该再改” | 重点是只读,不是显式强调编译期常量 |
constexpr int buf_size = 1024; |
语言语义层面 | 有 | 有 | 编译期常量表达式 | 数组大小、模板参数、constexpr 函数等需要编译期信息的场景 |
只在 C++ 里有,使用条件比 const 更严格 |
如果只抓最关键的一点:
#define是预处理器做文本替换const表达的是只读语义constexpr表达的是编译期常量语义
该怎么选
如果只是做预处理替换,或者做条件编译,用 #define 很正常:
1 |
如果是在 C++ 里定义普通常量,通常优先考虑 const 或 constexpr,尽量不要再回退到 #define。
- 只想表示“这个值不应该再改了”:用
const - 想明确表示“这是编译期常量”:用
constexpr - 需要做预处理替换或条件编译:用
#define
小结
最后收束成几句话:
- C 里的
const重点是“只读限制” - C++ 里的
const在引用、成员函数这些地方更常见也更重要 constexpr是 C++ 提供的“编译期常量”工具,语义比const更强- 在 C++ 里定义普通常量,优先考虑
const或constexpr
如果后面还要继续展开,这个话题还可以再单独补一篇,把 const、constexpr、consteval、constinit 放在一起看。