先说结论:
const
修饰的变量表示该变量的值不会发生变化,但是不一定能够在编译期间就取得结果。constexpr
修饰的变量,不仅变量值不会发生变化,而且在编译期间就能取得结果。所以如果一个 const 变量能够在编译期间求值,那么它就是一个常量表达式。常量(constant value),顾名思义就是变量值不会发生变化的变量。C++ 中可以通过 const
定义一个常量。
满足以下条件的变量即为常量表达式:
第一条表示变量是一个常量,所以常量表达式一定是一个常量,但是常量不一定是常量表达式。字面值一定是常量表达式。
int a = 4; // 不是常量表达式,因为 a 不是常量
const int b = a; // 不是常量表达式,因为初始值 a 不是常量表达式
const int c = 14; // 是常量表达式,因为 14 是字面值
const int d = c + 1; // 是常量表达式,因为 c + 1 是常量表达式
const int e = getNumer(); // 如果 getNumer() 是普通函数,e 的值要在运行时才能确定,所以不是常量表达式
常量表达式的使用场景:
通过上面的例子可以看出,判断一个 const 对象是否为常量表达式时需要一定的思考过程,如果代码很复杂,判断难度会越来越高。所以 C++11 允许把变量声明为 constexpr 类型,利用编译器取保证变量为常量表达式,如果声明为 constexpr 的变量不满足常量表达式的条件,编译将会报错。所以当看到一个变量声明为 constexpr 时,那么它一定是常量表达式。
int getValue() {
return 4;
}
int main()
{
constexpr int a = 4;
constexpr int b = getValue(); // error: function call must have a constant value in a constant expression
return 0;
}
可以用 constexpr 声明常量表达式函数,它是指可以用作常量表达式的函数。但是要记住 constexpr 函数不一定返回常量表达式!需要满足:返回类型和所有形参的类型必须是字面值类型(literal type)。除了内置类型,用户自定义的类也可以是字面值类型,因为它的构造函数和成员函数也可以是 constexpr 函数。
只有 constexpr 函数的所有实参都是常量表达式,constexpr 函数的结果才是常量表达式。只要有一个参数值在编译期间未知,那就和普通函数一样,在运行时计算。
constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // 正确:foo 是一个常量表达式
constexpr int sum(int a, int b) {
return a + b;
}
constexpr int i1 = 42;
constexpr int i2 = sum(i1, 52); // 所有参数都是常量表达式,sum 的结果也是常量表达式,在编译期求值
int AddThree(int i) {
return sum(i, 3); // i 不是常量表达式,此时 sum 作为普通函数使用
}
为了能保证 constexpr 函数在编译时能随时展开计算,constexpr 函数隐式内联。内联函数和 constexpr 函数不同于其他函数,允许定义多次,但要保证所有的定义一致。正因如此,内联函数和 constexpr 函数一般定义在头文件中。
简单提一下内联函数。可以通过 inline 关键字将函数声明为内联函数。例如:
inline const string& shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
cout << shorterString(s1, s2) << endl;
在编译过程中 cout << shorterString(s1, s2) << endl;
将会被展开成为以下形式:
cout << (s1.size() <= s2.size() ? s1 : s2) << endl;
这样可以消除 shorterString 运行时的开销。
inline 关键字只是向编译器发出一个请求,编译器可以选择忽略这个请求,将函数当作普通函数对待。
(完)