constexpr 是C++11中引入的关键字,声明为constexpr类型的变量,编译器会验证该变量的值是否是一个常量表达式,目的是将运算尽量放在编译阶段,而不是运行阶段。
- C++11中的constexpr指定的函数返回值和参数必须要保证是字面值,而且必须有且只有一行return代码,比如通常只能通过return 三目运算符+递归来计算返回的字面值。
- C++14中只要保证返回值和参数是字面值就行了,函数体中可以加入更多的语句
const并不能代表“常量”,只是对变量的一个修饰,告诉编译器这个变量只能被初始化,且不能被直接修改(实际上可以通过堆栈溢出等方式修改)。而这个变量的值,可以在运行时也可以在编译时指定。
constexpr可以用来修饰变量、函数、构造函数。一旦以上任何元素被constexpr修饰,那么等于说是告诉编译器 “请大胆地将我看成编译时就能得出常量值的表达式去优化我”。
const 和 constexpr 变量之间的主要区别在于:
- const 变量的初始化可以延迟到运行时,而 constexpr 变量必须在编译时进行初始化。所有 constexpr 变量均为常量,因此必须使用常量表达式初始化。
修饰函数
修饰函数的时候两者之间最基本的区别是:
- const只能用于非静态成员的函数而不是所有函数。它保证成员函数不修改任何非静态数据。
- constexpr可以用于含参和无参函数。constexpr函数适用于常量表达式,只有在下面的情况下编译器才会接受constexpr函数: 1.函数体必须足够简单,除了typedef和静态元素,只允许有return语句。如构造函数只能有初始化列表,typedef和静态元素 (实际上在C++14标准中已经允许定义语句存在于constexpr函数体内了) 2.参数和返回值必须是字面值类型
// 编译报错
//error : 函数调用在常量表达式中必须具有常量值
const int func() {
return 10;
}
main(){
int arr[func()];
}
//编译成功
constexpr func() {
return 10;
}
main(){
int arr[func()];
}
constexpr还有另外一个特性:会将计算任务留给运行时
当其检测到func的参数是一个常量字面值的时候,编译器才会去对其做优化
constexpr int sq(int n)
{
return n * n;
}
//通过 g++ a.cpp -S ,查看 assembly发现的确在编译时就计算出 123 的平方 15129 了。
int main()
{
constexpr int N = 123;
constexpr int N_SQ = sq(N);
printf("%d %d\n", N, N_SQ);
}
constexpr function 有非常硬的限制。从传入的参数到中间的运算流程都必须是编译期确切知道的,不然编译器根本不能计算。否则,会延迟到运行
constexpr int func(const int n){
return 10+n;
}
main(){
const int i = cin.get();
cout<<func(i);
}
//编译通过
开始 constexpr function 里面是不能出现如 if for 这样的流程控制的,必须一步到位计算結果,函数中间也不得出现 n++ 这类的表达式。可以把 constexpr function 整体想成一个包起来的一行的表达式。一行表达式里不允许再内嵌一个子表达式。
C++14 之后, if 可以写。反正 if 内的参数只要也是 constexpr statement,就会编译计算。也可以写超过一个return 敘述。
修饰结构体
#include <iostream>
using namespace std;
class Rectangle
{
int _h, _w;
public:
// 修饰一个结构体
constexpr Rectangle (int h, int w) : _h(h), _w(w) {}
// 修饰一个函数,_h, _w为全局,并且在实例化时就已经是初始化后的常量了
constexpr int getArea () const { return _h * _w; }
};
int main()
{
// 对象在编译时就已经初始化了
constexpr Rectangle obj(10, 20);
cout << obj.getArea();
return 0;
}
在enum,switch中的应用
constexpr int fib(int n)
{
if (n <= 0) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
enum FIB_ENUM {
a = fib(5),
b = fib(10),
c = fib(20),
dummy = fib(0)
};
int main()
{
FIB_ENUM my_fib = a;
printf("%d %d %d\n", my_fib, FIB_ENUM::b, FIB_ENUM::c);
// 8 89 10946
}
同样, switch 的逻辑分支也可以把各个 case 替换成各种 constexpr 的结果
什么时候用constexpr?
在函数中若有常量表达式那么必须用constexpr,仅仅满足常量表达式的条件是不够的
template <int N>
class list
{ };
constexpr int sqr1(int arg)
{ return arg*arg; }
int sqr2(int arg)
{ return arg*arg; }
int main()
{
const int X = 2;
list<sqr1(X)> mylist1;//可以,因为sqr1时constexpr函数
list<sqr2(X)> mylist2;//不可以,因为sqr2不是constexpr函数
return 0;}
- 修饰变量
修饰变量时没有必要同时使用const和constexpr 因为constexpr包含了 const的含义
constexpr const int N = 5;
constexpr int N = 5;
//两行的意思完全相同
然而注意有一些情况const和constexpr在修饰不同的东西
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
return 0;
}
在这里constexpr和const都必须要有。constexpr表示NP指针本身是常量表达式,而const表示指向的值是一个常量。去掉const之后无法编译,因为不能用正常指针指向常量。
- 修饰成员函数
在C++11中,对成员函数而言constexpr同样包含const的含义。然而在C++14中可能已经改变了
constexpr int f();
# 改为
constexpr int f() const;