【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?
【C++ 面试真题】C++ 的 const 和 constexpr 有什么区别?
摘要:
const管"能不能改"(只读语义),constexpr管"能不能在编译期算出来"(编译期常量)。const变量可能运行期才确定值,constexpr变量必须编译期可知且隐含const;constexpr函数可以"双向"运行——编译期输入得编译期结果,运行期输入则退化为普通函数。本文用问答方式逐层拆解,涵盖核心区别、经典误区、C++11→C++20 演进及面试高频追问。
上期聊完
static,文末埋了个坑:下期聊const和constexpr。这俩长得像、都跟"常量"沾边,却是面试里最容易翻车的一对概念。很多同学张口就答"const 是常量",结果被一句"const int n = rand();合法吗"直接带走。本文用问答的方式,把它们一次掰扯清楚。
一、先说结论
❓ 说说 C++ 中的 const 和 constexpr 有什么本质区别?
✅ const和constexpr的本质区别,概括起来就是:
- const修饰的是只读语义——“我承诺不写它,但不保证它是什么时候算出来的”;
- constexpr修饰的是编译期可求值——“它的值必须在编译阶段就能算出来”。
最小对比例子:
// 只读,但值运行期才确定constinta=rand();// ❌ a 不能当编译期常量用// 编译期就能算出来constexprintb=3*7;// ✅ b == 21,编译期常量a合法、但不是编译期可知;b必须编译期可知。这就是全部差异的根。
💡记一个口诀:const 管"能不能改",constexpr 管"能不能在编译期算出来"。前者是访问权限,后者是求值时机。
一点历史背景:const从 C 时代就有(C++ 沿用并扩展),原本就是"只读"的语义;而constexpr是C++11才引入的新关键字,专门用来表达"编译期常量"。在constexpr出现之前,C++ 程序员想定义编译期常量,只能借助const加常量初始化这种"约定俗成"的写法,语义含糊。constexpr的诞生,就是要把"编译期可知"这件事明明白白地写出来,让编译器替你把关——不满足就编译报错,而不是留到运行期才发现。
二、const 到底保证了什么?
❓ 那 const 真的等于"常量"吗?
✅ 不等于。const 保证的是只读,不是编译期可知。看这个经典反例:
#include<cstdlib>intmain(){// 值运行期才确定constintn=rand();// 之后只读,没问题// n = 10; // ❌ 编译错:只读}n是 const,合法——因为它初始化后不再改变。但它的值来自rand(),编译器根本没法在编译期知道它是多少。
后果是:凡是要"编译期常量"的地方,n都用不了:
// 设 const int n = rand();std::array<int,n>a;// ❌ 数组大小switch(x){casen:// ❌ case 标签break;}T<n>obj;// ❌ 模板实参但反过来,如果 const 变量恰好用常量表达式初始化,它就同时也是编译期常量:
constintN=10;// 常量表达式初始化intarr[N];// ✅ 可以当数组大小所以更准确的说法是:const 是"只读",至于它是不是编译期常量,要看它的初始化方式。
再补一个容易混的细节:const 修饰"对象本身只读",并不限制它的来源。它可以来自rand()、来自文件读入、来自函数返回值——只要初始化之后不再修改,就满足 const 的契约。
💡加分点:const 的反义词不是"变量",而是
mutable。它管的是访问权限(只读),不是值的来源时间。const 最实用的价值其实是契约——函数参数写成const T&,是在告诉调用方"我不会改你的数据"。顺带提一句:在
const成员函数里,mutable成员仍可修改。这是 const 唯一的"后门",常用于缓存、互斥锁等"逻辑上不影响对象状态"的场景。
三、constexpr 到底保证了什么?
❓ 那 constexpr 又保证了什么?
✅ constexpr 保证:被它修饰的东西,值能在编译期算出来。分两种用法。
1. constexpr 变量——必须是编译期常量,且必须立刻初始化、隐含 const:
constexprintx=10;// 编译期常量// x = 5; // ❌ 隐含 const,只读intbuf[x];// ✅ 可当数组大小2. constexpr 函数——“在给定编译期输入时,能在编译期算出结果”:
constexprintsquare(intn){returnn*n;}intmain(){// 入参是编译期值constexprinta=square(5);// ✅ 25// 入参是运行期值intb=square(rand());// ↑ 按普通函数在运行期跑}⚠️重点(高频考点):constexpr 函数不是"只能在编译期跑"。传入运行期值时,它就退化成普通函数在运行期执行。它的语义是"可以在编译期跑",不是"只能在编译期跑"。这是一个非常大的理解误区。
怎么判断这次调用到底在哪个阶段跑?看接收结果的"容器":
- 结果赋给
constexpr变量、或用在static_assert、模板实参、数组大小里——编译器强制在编译期求值,入参也必须是编译期值; - 结果赋给普通变量——可以在运行期跑(此时入参无所谓),编译器自行决定要不要在编译期优化掉。
一句话:constexpr 函数是"双向"的,跑在哪一端,由调用点决定。
四、核心区别对比表
| 维度 | const | constexpr |
|---|---|---|
| 管什么 | 只读语义 | 编译期可求值 |
| 求值时机 | 运行期也行 | 必须编译期可知 |
| 运行期初始化 | ✅ 允许 | ❌ 禁止 |
| 当数组/模板实参 | 视初始化而定 | ✅ 一定可以 |
| 当 case 标签 | 视初始化而定 | ✅ 一定可以 |
| 修饰函数 | ❌ 不能 | ✅ 可以 |
| 是否互相蕴含 | 不蕴含 constexpr | 变量隐含 const |
五、二者不是互斥,而是可叠加
❓ 那能同时写 const 和 constexpr 吗?
✅ 能,但对变量来说,写 constexpr 就够了——constexpr 变量隐含 const,再写 const 是多余的:
constexprintx=5;// 已隐含 constconstexprconstinty=5;// ✅ 合法但多余真正常组合用的是和static,用来在类里定义编译期常量:
classC{staticconstexprintk=8;// C++11:类内可初始化// 但取地址仍需类外定义// C++17:constexpr 隐含// inline,类外不必再写};🎯指针这块最容易搞混。
const T*和T* const是"指向 const"和"const 指针"的区别,而constexpr 指针说的是"指针本身是编译期常量",跟它指向的对象是否 const无关:intg=1;// constexpr 修饰"指针本身"constexprint*p=&g;*p=2;// ✅ 写 g,合法// 注意:指向的对象 g 非 const
六、经典误区与陷阱
❓ 实际写代码最容易踩哪些坑?
✅ 三个典型误区,全是面试和实战的高发区。
误区①:把 const 当编译期常量
constintsz=get_size();// 运行期值// int arr[sz]; // ❌ VLA// 应改用:std::vector<int>v(sz);// ✅ 运行期大小误区②:以为 constexpr 函数"必须"编译期执行
constexprintadd(inta,intb){returna+b;}// 运行期调用,完全合法intx=add(rand(),rand());// 不报错,当普通函数跑误区③:本该用 constexpr,却写成 const,白白丢优化
// 前提:factorial 是 constexprconstexprintfactorial(intn){returnn<=1?1:n*factorial(n-1);}constintv=factorial(5);// ⚠ 不保证编译期算,可能留到运行期constexprintv2=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::string、std::vector都能在 constexpr 上下文里用了,"编译期计算"的能力大涨。甚至能在编译期排序、做字符串处理。这也是为什么现在越来越多的库函数(std::numeric_limits、std::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 函数也能在运行期跑",这题就稳了。
如果这篇对你有帮助,欢迎收藏 📌。下期我们聊聊auto与decltype的那些坑,敬请关注 👋
