c++11 constexpr常量表达式

在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对常量表达式函数有几点要求:

  1. 常量表达式函数必须有返回值(不可以是void函数)。
  2. 常量表达式函数体中只能有一条语句,且该语句必须是return语句。但不产生实际代码的语句可以在常量表达式函数中使用,如static_assert,using,typedef等。
  3. 常量表达式函数在使用前,必须有定义。(普通函数在被调用前只要有函数声明就够了,不一定有定义)举例如下:

    constexpr int f();    //常量表达式函数声明
    constexpr int c = f();  // 无法通过编译
    constexpr int f() { return 1; }
    constexpr int d = f(); //可以通过编译
    
  4. 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语句不是常量表达式,不能通过编译
    

常量表达式函数不一定是在编译时产生返回值的,这取决于调用该函数时,传给它的实参是否是常量表达式:

  1. 如果实参都是常量表达式的话,那么它可以在编译时产生返回值。
  2. 其它情况下,常量表达式函数跟普通函数一样,只有在运行时才能被调用,产生返回值。

举例:

//常量表达式定义
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;
}

常量构造函数的约束条件:

  1. 成员变量只能通过初始化列表来初始化,函数体必须为空
  2. 初始化列表只能由常量表达式来赋值

常量成员函数的约束条件:

  1. 常量成员函数被隐式定义为const成员函数,不可以通过常量成员函数去修改成员变量。也就是说,常量成员函数往往是所谓的getter函数。(c++14则不同,允许constexpr成员函数去修改成员变量)
  2. 常量成员函数不能是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;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值