在c++中,当我们用到常量时,往往是通过const关键字来修饰的:
const int x = 3;
但大多数情况下,const描述的都是一些“运行时常量”,即该变量具有运行时的不可修改性。不过有时候,我们需要某些变量在编译时保持常量性,如:
const int size = 10;
int arr[size]; //编译不通过
常量表达式变量
const关键字无法保证编译时的常量性,因此c++11引入了新的关键字constexpr
。
constexpr
是常量表达式的修饰符,用于修饰编译时可得到其value的表达式(对于c++11,只能是内置类型的变量)。如果你希望得到一个可在编译时使用的变量,那应该选择constexpr
而不是const
:
constexpr int size = 10;
int arr[size]; //编译通过
当用constexpr修饰对象时,该对象视为编译时的常量,且该变量必须由编译时的常量初始化(如宏定义、字面常量、其它常量表达式等),举例如下:
#define VALUE 5
constexpr int x = VALUE;
constexpr int y = 3;
constexpr int z = x;
常量表达式函数
constexpr除了可以修饰内置类型的变量外,还可以修饰函数。但c++11对常量表达式函数有几点要求:
- 常量表达式函数必须有返回值(不可以是void函数)。
- 常量表达式函数体中只能有一条语句,且该语句必须是return语句。但不产生实际代码的语句可以在常量表达式函数中使用,如
static_assert
,using
,typedef
等。常量表达式函数在使用前,必须有定义。(普通函数在被调用前只要有函数声明就够了,不一定有定义)举例如下:
constexpr int f(); //常量表达式函数声明 constexpr int c = f(); // 无法通过编译 constexpr int f() { return 1; } constexpr int d = f(); //可以通过编译
return语句中,不能使用非常量表达式的变量、函数,且return的表达式也要是常量表达式。举例:
const int e() { return 1; } //非常量表达式 constexpr int g() { return e(); }//调用了非常量表达式,不能通过编译 const int g = 3; constexpr int h() { return g; }//调用了非常量表达式,不能通过编译 constexpr int k(int x) { return x = 1; } //return语句不是常量表达式,不能通过编译
常量表达式函数不一定是在编译时产生返回值的,这取决于调用该函数时,传给它的实参是否是常量表达式:
- 如果实参都是常量表达式的话,那么它可以在编译时产生返回值。
- 其它情况下,常量表达式函数跟普通函数一样,只有在运行时才能被调用,产生返回值。
举例:
//常量表达式定义
constexpr int func(int x) { return x+1; }
int y = 10;
int arr_size = func(y);//arr_size的值只有在运行时才可以得到
int arr[arr_size];//编译失败
constexpr int z = 10;
constexpr int vec_size = func(z);//vec_size的值是在编译时得到的
int vec[vec_size];//编译成功
常量构造函数和常量成员函数
C++11标准中,constexpr不能用于修饰自定义类型的对象,如下面的代码是不能通过编译的:
constexpr struct MyType { int x; }
constexpr MyType mt {0};
但是,我们可以定义常量构造函数和成员函数,比如:
#include <iostream>
#include <array>
class arr_range {
public:
//常量构造函数
constexpr arr_range(size_t first, rsize_t second) noexcept
: _first(first), _second(second) {}
//常量成员函数
constexpr size_t first() const noexcept { return _first; }
constexpr size_t second() const noexcept { return _second; }
private:
rsize_t _first, _second;
};
int main()
{
//通过常量构造函数定义常量表达式
constexpr arr_range ar{ 2, 3 };//编译时常量
//编译时调用常量成员函数
std::array<std::array<int, ar.second()>, ar.first()> arr;
std::cout << "arr.first_size = " << arr.size() << std::endl;
std::cout << "arr.second_size = " << arr.at(0).size() << std::endl;
//用于数组
int tmp[ar.first()][ar.second()] { {1, 2, 3}, {4, 5, 6} };
for (auto& elem : tmp)
{
for (auto& num : elem)
std::cout << num << " ";
puts("");
}
//用constexpr初始化enum
enum index { first = ar.first(), second = ar.second() };
std::cout << "first = " << first << std::endl;
std::cout << "second = " << second << std::endl;
return 0;
}
常量构造函数的约束条件:
- 成员变量只能通过初始化列表来初始化,函数体必须为空
- 初始化列表只能由常量表达式来赋值
常量成员函数的约束条件:
- 常量成员函数被隐式定义为const成员函数,不可以通过常量成员函数去修改成员变量。也就是说,常量成员函数往往是所谓的
getter
函数。(c++14则不同,允许constexpr成员函数去修改成员变量)- 常量成员函数不能是virtual的
常量表达式函数可以是递归的
符合c++11标准的编译器对常量表达式函数应该至少支持512层的递归。举例:
#include <iostream>
using namespace std;
constexpr int Fibonacci(int n) {
return (n == 1) ? 1 : ((n == 2) ? 1 : Fibonacci(n - 1) + Fibonacci(n - 2));
}
int main() {
int fib[] = {
Fibonacci(11), Fibonacci(12),
Fibonacci(13), Fibonacci(14),
Fibonacci(15), Fibonacci(16)
};
for (int i : fib) cout << i << endl;
}