const可以用于定义变量,它的值不能被改变。
const int bufSize = 512;
如果有代码试图修改这个变量,就会发生编译错误
const当前也可以用于修改指针,但是会带来一些混乱,比如:
const double pi = 3.14;
const double *cptr = π //指向常数的指针
*cptr = 4; //编译错误
double var = 45;
double *const pvar = &var; //指向变量为常数
*pvar = 40; //正确
const限定符只能保证初始化之后不会被修改,而变量的值可以在编译阶段确定,也可以在执行时确定。
C++中的常量表达式指的是固定并且在编译过程就能确定计算结果的表达式
比如:
const int max_files = 20; //20是字面值
const int limit = max_files + 1; //max_files为常量表达式,1是字面值。
const int sz = get_size(); //sz不是常量表达式。
上面的表达式同时满足两个条件:
如果变量类型不是const,那么无论它的初始值是不是常量表达式,它就只是一个变量;如果初始值不是常量表达式,那个变量的值就无法在编译期间确定只能在执行时占用CPU开销来赋值
变量的值在编译期间决定,带来的好处是提高效率,例如例子中的limit的表达方式,在照顾可读性的同时,又不会影响执行效率
constexpr
:指明变量或者函数的值可以在常量表达式中出现
为什么要引入常量表达式:
有了const,为什么还要引入constexpr:
一般来说,在日益复杂的系统中确定变量的初始值到底是不是常量表达式并不是一件容易的事情。为了解决这个问题C++11允许将变量声明为costexpr类型以便由编译器验证变量的值是否是一个常量表达式
变量声明为constexpr类型,就意味着一方面变量本身是常量,也意味着它必须用常量表达式来初始化:
constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr float y{108};
constexpr int i; // Error! Not initialized
int j = 0;
constexpr int k = j + 1; //Error! j not a constant expression
如果初始值不是常量表达式,就会发生编译错误
除了能用常量表达式初始化constexpr变量声明为,还可以使用consstexpr函数。它是指能用于常量表达式的函数,也就是它的计算结果可以在编译时确定。。
例如下面的计算阶乘的constexpr函数。
constexpr long long factorial(int n){
return n <= 1? 1 : (n * factorial(n - 1));
}
constexpr long long f18 = factorial(20);
可以用它来初始化constexp变量。所有计算都在编译时完成,比较有趣的是像溢出这样的错误也会在编译期检出
定义的方法就是在返回值类型前加constexpr关键字。但是为了保证计算结果可以在编译期就确定,必须满足以下条件:
return
语句也就是说函数体中只能有一条语句,而且该语句必须是return语句,因此,下面:
constexpr int data(){
const i = 1;
return i;
}
无法通过编译。不过一些不会产生实际代码的语句在常量表达式函数的使用下,可以通过编译,比如说static_assert,using、typedef之类的。因此,下面:
constexpr int f(int x){
static_assert(0 == 0, "assert fail.");
return x;
}
可以通过编译。
看个例子:
constexpr int f();
int a = f();
const int b = f();
constexpr int c = f(); // 无法通过编译
constexpr int f() { return 0;}
constexpr int k = f();
在a和b的定义中,编译器会将f()转换为一个函数调用,而在c的定义中,由于是一个常量表达式的值,因此,会要求编译器进行编译时值计算。这时候由于f还没有定义,就会导致编译错误。
const int f(){
return 1;
}
constexpr int g(){
return f();
}
int g = 3;
constexpr int b(){
return g;
}
不能通过编译,因为return表达式不能包含运行时才能确定的返回值
总结
简单地说,constexpr可以:
聚合类允许调用者直接访问其成员,并且具有特殊的初始化形式。聚合类满足下面条件:
怎么理解呢?
首先,聚合类其实就是一个C结构体;其次,聚合这个词,应该是相对组合的,表明了成员和类之间的松散关系。
当一个类是聚合类时,就可以使用初始值列表向下面这样初始化了。
struct Point{
int x;
int y;
};
Point pt = {10, 10};
我们知道,constexpr函数的参数和返回值都必须是常量表达式。而常量表达式的最基本要素就是字面值类型。字面值类型除了包括算数类型,引用和指针之外,某些类也属于字面值类型,C++称之为字面值常量类。主要包括两种情况:
constexpr Point(int _x, int _y)
:x(_x),y(_y){}
constexpr Point()
:Point(0,0){}
int x;
int y;
};
constexpr Point pt = {10, 10};
这样声明以后,就可以在使用constexpr表达式或者constexpr函数的地方使用字面值常量类了。
// constexpr.cpp
// Compile with: cl /EHsc /W4 constexpr.cpp
#include
using namespace std;
// Pass by value
constexpr float exp(float x, int n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp(x * x, n / 2) :
exp(x * x, (n - 1) / 2) * x;
}
// Pass by reference
constexpr float exp2(const float& x, const int& n)
{
return n == 0 ? 1 :
n % 2 == 0 ? exp2(x * x, n / 2) :
exp2(x * x, (n - 1) / 2) * x;
}
// Compile-time computation of array length
template<typename T, int N>
constexpr int length(const T(&)[N])
{
return N;
}
// Recursive constexpr function
constexpr int fac(int n)
{
return n == 1 ? 1 : n * fac(n - 1);
}
// User-defined type
class Foo
{
public:
constexpr explicit Foo(int i) : _i(i) {}
constexpr int GetValue() const
{
return _i;
}
private:
int _i;
};
int main()
{
// foo is const:
constexpr Foo foo(5);
// foo = Foo(6); //Error!
// Compile time:
constexpr float x = exp(5, 3);
constexpr float y { exp(2, 5) };
constexpr int val = foo.GetValue();
constexpr int f5 = fac(5);
const int nums[] { 1, 2, 3, 4 };
const int nums2[length(nums) * 2] { 1, 2, 3, 4, 5, 6, 7, 8 };
// Run time:
cout << "The value of foo is " << foo.GetValue() << endl;
}
我们知道,constexpr
将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高。C++17支持if constexpr(表达式),允许在代码中声明常量表达式的判断条件,如下:
#include
template<typename T>
auto print_type_info(const T & t){
if constexpr (std::is_integral<T>::value){
return t + 1;
}else{
return t + 0.01;
}
}
int main() {
std::cout << print_type_info(5) << "\n";
std::cout << print_type_info(5.5) << "\n";
}