1. const 的局限性

const 修饰变量代表这个变量不能被修改,但它不一定是编译期常量,也就是说它不能区分编译期常量和运行期常量,它的值完全可以运行时才确定。

#include <iostream>
int get_runtime_val()
{return 666;
}
int main()
{const int c1 = 10;const int c2 = get_runtime_val();return 0;
}
  • c1是字面量初始化,编译器可以识别为常亮,只读,允许运行时初始化。
  • c2是运行调用get_runtime_val()得到,仅仅是运行时只读变量,不能用做数组大小、模板参数。

const修饰只读,运行时可以初始化;constexpr修饰编译期可求值,要求编译期阶段就能算出来。

2. constexpr修饰变量(c++11)

被constexpr修饰的变量,必须编译期就能得到结果。
其语法格式:
constexpr 类型 变量名 = 常量表达式;

constexpr int A = 20;
constexpr int B = A * 3 + 5;
int arr[A];          // 标准C++允许,编译期常量
int arr2[B];         // 
// constexpr int C = get_num(); // 报错,get_num运行时调用,不是常量表达式
  • constexpr变量初始化表达式,必须是常量表达式
  • 不允许运行时函数返回初始化
  • constexpr变量隐式带上const属性,不能修改
    image

3. constexpr 修饰函数(C++11 ~ C++17 变化)

C++11 constexpr 函数限制非常严格

  • 函数里面只能有一条 return 语句;
  • 不能有局部变量;
  • 不能循环、if;
  • 参数、返回值必须是字面值类型;
// C++11合法constexpr函数
constexpr int calc(int x)
{return x * x + 2;
}
int main()
{constexpr int res = calc(10); // 编译期计算 res = 102int arr[res];                 // 数组大小int a = 5;int res2 = calc(a);          // 也可以运行时调用,普通函数使用return 0;
}

constexpr 函数有双重能力:

  • 如果传入常量表达式实参,则在编译期计算;
  • 如果传入普通运行时变量,退化成普通函数,运行时执行。

C++14 放松 constexpr 函数限制

C++14:constexpr 函数允许局部变量、循环、if 分支,函数体内可以有多条语句。

// C++14 可以写循环
constexpr int factorial(int n)
{int ret = 1;for(int i = 2; i <= n; ++i){ret *= i;}return ret;
}
int main()
{constexpr int f5 = factorial(5); // 编译期算出120return 0;
}

C++17:constexpr if(编译期分支)

template<typename T>
void func(T t)
{if constexpr(sizeof(T) == 4){// 仅int等4字节类型编译这一段代码}else{// 其他类型编译这一段}
}

4. constexpr 修饰构造函数(常量表达式对象)

C++11 允许constexpr构造函数,可以在编译期构造对象。
要求:构造函数体内不能有复杂逻辑,成员全部常量初始化。

struct Point
{int x;int y;// constexpr构造函数constexpr Point(int a, int b) : x(a), y(b){}
};
int main()
{// 编译期就构造完成对象,不占用运行时constexpr Point p{10,20};static_assert(p.x == 10, "error"); //编译期断言,条件不满足直接编译报错,运行时无开销。return 0;
}

5. constexpr 与 const 总结

特性 const constexpr
主要作用 只读保护 编译期求值
变量初始化 可以运行时初始化 必须常量表达式
修饰函数 不能修饰函数(C++ 前) C++11 起支持编译期函数
数组大小 不一定可用 可以做编译期数组大小
对象 可以修饰运行时对象 可以编译期构造对象
是否隐式带 const