c++ const和constexpr

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;

本文链接:https://my.lmcjl.com/post/2395.html

展开阅读全文

4 评论

留下您的评论.