【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?

【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?

摘要const 管"能不能改 "(只读语义),constexpr 管"能不能在编译期算出来 "(编译期常量)。const 变量可能运行期才确定值,constexpr 变量必须编译期可知且隐含 constconstexpr 函数可以"双向"运行------编译期输入得编译期结果,运行期输入则退化为普通函数。本文用问答方式逐层拆解,涵盖核心区别、经典误区、C++11→C++20 演进及面试高频追问。
上期聊完 static,文末埋了个坑:下期聊 constconstexpr。这俩长得像、都跟"常量"沾边,却是面试里最容易翻车 的一对概念。很多同学张口就答"const 是常量",结果被一句"const int n = rand(); 合法吗"直接带走。本文用问答的方式,把它们一次掰扯清楚。


一、先说结论

❓ 说说 C++ 中的 const 和 constexpr 有什么本质区别?

✅ const和constexpr的本质区别,概括起来就是:

  • const 修饰的是只读语义------"我承诺不写它,但不保证它是什么时候算出来的";
  • constexpr 修饰的是编译期可求值------"它的值必须在编译阶段就能算出来"。

最小对比例子:

cpp 复制代码
// 只读,但值运行期才确定
const int a = rand();
// ❌ a 不能当编译期常量用

// 编译期就能算出来
constexpr int b = 3 * 7;
// ✅ b == 21,编译期常量

a 合法、但不是编译期可知;b 必须编译期可知。这就是全部差异的根。

💡 记一个口诀 :const 管"能不能改 ",constexpr 管"能不能在编译期算出来"。前者是访问权限,后者是求值时机。

一点历史背景const 从 C 时代就有(C++ 沿用并扩展),原本就是"只读"的语义;而 constexprC++11 才引入的新关键字,专门用来表达"编译期常量"。在 constexpr 出现之前,C++ 程序员想定义编译期常量,只能借助 const 加常量初始化这种"约定俗成"的写法,语义含糊。constexpr 的诞生,就是要把"编译期可知"这件事明明白白地写出来,让编译器替你把关------不满足就编译报错,而不是留到运行期才发现。


二、const 到底保证了什么?

❓ 那 const 真的等于"常量"吗?

✅ 不等于。const 保证的是只读 ,不是编译期可知。看这个经典反例:

cpp 复制代码
#include <cstdlib>
int main() {
    // 值运行期才确定
    const int n = rand();
    // 之后只读,没问题
    // n = 10;   // ❌ 编译错:只读
}

n 是 const,合法------因为它初始化后不再改变。但它的值来自 rand()编译器根本没法在编译期知道它是多少

后果是:凡是要"编译期常量"的地方,n 都用不了:

cpp 复制代码
// 设 const int n = rand();
std::array<int, n> a;   // ❌ 数组大小
switch (x) {
    case n:             // ❌ case 标签
        break;
}
T<n> obj;              // ❌ 模板实参

但反过来,如果 const 变量恰好用常量表达式初始化 ,它就同时也是编译期常量

cpp 复制代码
const int N = 10;      // 常量表达式初始化
int arr[N];            // ✅ 可以当数组大小

所以更准确的说法是:const 是"只读",至于它是不是编译期常量,要看它的初始化方式

再补一个容易混的细节:const 修饰"对象本身只读",并不限制它的来源。它可以来自 rand()、来自文件读入、来自函数返回值------只要初始化之后不再修改,就满足 const 的契约。

💡 加分点 :const 的反义词不是"变量",而是 mutable。它管的是访问权限 (只读),不是值的来源时间 。const 最实用的价值其实是契约 ------函数参数写成 const T&,是在告诉调用方"我不会改你的数据"。

顺带提一句:在 const 成员函数里,mutable 成员仍可修改。这是 const 唯一的"后门",常用于缓存、互斥锁等"逻辑上不影响对象状态"的场景。


三、constexpr 到底保证了什么?

❓ 那 constexpr 又保证了什么?

✅ constexpr 保证:被它修饰的东西,值能在编译期算出来。分两种用法。

1. constexpr 变量 ------必须是编译期常量,且必须立刻初始化隐含 const

cpp 复制代码
constexpr int x = 10;   // 编译期常量
// x = 5;   // ❌ 隐含 const,只读
int buf[x];             // ✅ 可当数组大小

2. constexpr 函数------"在给定编译期输入时,能在编译期算出结果":

cpp 复制代码
constexpr int square(int n) {
    return n * n;
}
int main() {
    // 入参是编译期值
    constexpr int a = square(5);  // ✅ 25
    // 入参是运行期值
    int b = square(rand());
    // ↑ 按普通函数在运行期跑
}

⚠️ 重点(高频考点) :constexpr 函数不是"只能在编译期跑" 。传入运行期值时,它就退化成普通函数在运行期执行。它的语义是"可以 在编译期跑",不是"只能在编译期跑"。这是一个非常大的理解误区。

怎么判断这次调用到底在哪个阶段跑? 看接收结果的"容器":

  • 结果赋给 constexpr 变量、或用在 static_assert、模板实参、数组大小里------编译器强制在编译期求值,入参也必须是编译期值;
  • 结果赋给普通变量------可以在运行期跑(此时入参无所谓),编译器自行决定要不要在编译期优化掉。

一句话:constexpr 函数是"双向"的,跑在哪一端,由调用点决定。


四、核心区别对比表

维度 const constexpr
管什么 只读语义 编译期可求值
求值时机 运行期也行 必须编译期可知
运行期初始化 ✅ 允许 ❌ 禁止
当数组/模板实参 视初始化而定 ✅ 一定可以
当 case 标签 视初始化而定 ✅ 一定可以
修饰函数 ❌ 不能 ✅ 可以
是否互相蕴含 不蕴含 constexpr 变量隐含 const

五、二者不是互斥,而是可叠加

❓ 那能同时写 const 和 constexpr 吗?

✅ 能,但对变量 来说,写 constexpr 就够了 ------constexpr 变量隐含 const,再写 const 是多余的:

cpp 复制代码
constexpr int x = 5;        // 已隐含 const
constexpr const int y = 5;  // ✅ 合法但多余

真正常组合用的是和 static,用来在类里定义编译期常量:

cpp 复制代码
class C {
    static constexpr int k = 8;
    // C++11:类内可初始化
    // 但取地址仍需类外定义
    // C++17:constexpr 隐含
    // inline,类外不必再写
};

🎯 指针这块最容易搞混const T*T* const 是"指向 const"和"const 指针"的区别,而 constexpr 指针 说的是"指针本身 是编译期常量",跟它指向的对象是否 const 无关

cpp 复制代码
int g = 1;
// constexpr 修饰"指针本身"
constexpr int* p = &g;
*p = 2;   // ✅ 写 g,合法
// 注意:指向的对象 g 非 const

六、经典误区与陷阱

❓ 实际写代码最容易踩哪些坑?

✅ 三个典型误区,全是面试和实战的高发区。

误区①:把 const 当编译期常量

cpp 复制代码
const int sz = get_size();   // 运行期值
// int arr[sz];              // ❌ VLA
// 应改用:
std::vector<int> v(sz);      // ✅ 运行期大小

误区②:以为 constexpr 函数"必须"编译期执行

cpp 复制代码
constexpr int add(int a, int b) {
    return a + b;
}
// 运行期调用,完全合法
int x = add(rand(), rand());
// 不报错,当普通函数跑

误区③:本该用 constexpr,却写成 const,白白丢优化

cpp 复制代码
// 前提:factorial 是 constexpr
constexpr int factorial(int n) {
    return n <= 1 ? 1
             : n * factorial(n - 1);
}
const int v = factorial(5);
// ⚠ 不保证编译期算,可能留到运行期
constexpr int v2 = factorial(5);
// ✅ 逼编译期算出来

🔧 实操建议 :能加 constexpr 就加。它是超集------既能在编译期算(优化、当常量),又能在运行期跑。现代 C++ 鼓励把能在编译期算的东西都标成 constexpr。


七、constexpr 函数的演进(C++11 → C++20)

❓ constexpr 函数的限制是不是越来越松了?

✅ 对,每个版本都在大幅放宽:

版本 函数体里允许什么
C++11 单条 return,极简
C++14 if / for / 局部变量
C++17 if constexpr
C++20 new / try / string / vector

💡 加分点 :C++20 起,连 std::stringstd::vector 都能在 constexpr 上下文里用了,"编译期计算"的能力大涨。甚至能在编译期排序、做字符串处理。这也是为什么现在越来越多的库函数(std::numeric_limitsstd::vector 的部分接口)都加了 constexpr。


八、面试高频追问

Q1:const 变量能当数组大小吗?

看初始化。const int N = 10; ✅ 可以(编译期可知);const int N = f(); ❌ 不行(运行期才知)。

Q2:constexpr 函数在运行期调用,结果还算常量表达式吗?

不算。只有当本次调用的入参都是编译期值时,结果才是编译期常量;否则它退化成普通函数,结果就是运行期值。

Q3:const 和 constexpr 能同时写吗?

能。变量上:constexpr 已隐含 const,再加 const 多余。成员函数上二者正交 ,完全可以同时出现,例如 constexpr int f() const; 表示一个"编译期可求值、且不修改对象的成员函数"。

Q4:constexpr 函数能调用普通函数吗?

不能直接调用非 constexpr 函数(在编译期求值的路径上)。C++14 起可以自由调用其他 constexpr 函数。所以想让一个函数能在编译期用,它调用的下游函数也得是 constexpr。

Q5:constexpr 变量能不初始化吗?

不能。constexpr 变量必须 在声明处用常量表达式初始化,否则编译报错------这跟 const 不一样,const 可以只声明不初始化(如 extern const int n;)。


九、总结速查表

特性 const constexpr
本质 只读 编译期常量
运行期初始化
当数组/模板实参 视初始化而定
当 case 标签 视初始化而定
修饰函数
修饰变量能否不初始化 不能
互相蕴含 不蕴含 隐含 const

一句话回顾

const 管"能不能改 ",constexpr 管"能不能在编译期算出来"。前者是访问权限,后者是求值时机------记住这一句,再加一句"constexpr 变量隐含 const、constexpr 函数也能在运行期跑",这题就稳了。

如果这篇对你有帮助,欢迎收藏 📌。下期我们聊聊 autodecltype 的那些坑,敬请关注 👋

相关推荐
·薯条大王4 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
kyriewen7 小时前
面试官问"这段逻辑为什么这样写"——我才发现,这半年我写的代码,我自己都解释不了
前端·javascript·面试
hansang_IR9 小时前
【题解】LC:倍增 / 区间并查集(Range Parallel Unionfind)
c++·算法·并查集
东东最爱敲键盘9 小时前
day7.c++继承
c++
大龄秃头程序员10 小时前
深入梳理 UIView touch 事件全链路原理与实战
面试
沫璃染墨10 小时前
《从零入门Linux系统篇(十五):系统工具篇·六——GDB调试器详解:从程序执行控制到高级调试技巧》
linux·运维·服务器·c语言·c++
众人皆醒我独醉10 小时前
Token:AI 世界的基本粒子——不是"字",是统计上最优的"字符组合"
后端·面试·llm
吃着火锅x唱着歌10 小时前
Effective C++ 学习笔记 条款40 明智而审慎地使用多重继承
c++·笔记·学习
醉城夜风~11 小时前
(超详细)C++ STL list容器详解:从使用方法到双向链表底层原理全面解析
c++·windows
古法安卓11 小时前
Android-DeviceStorageMonitorService 流程分析
java·面试·android studio