C++11之常量表达式(const与constexpr的区别)

系列文章

C++11之正则表达式(regex_match、regex_search、regex_replace)

C++11之线程库(Thread、Mutex、atomic、lock_guard、同步)

C++11之智能指针(unique_ptr、shared_ptr、weak_ptr、auto_ptr)浅谈内存管理

C++11之强制类型转换(static_cast,const_cast,dynamic_cast,reinterpret_cast)

C++11之Lanbda表达式(匿名函数)

C++11之委派构造函数

C++11之模板的别名

C++11之追踪返回类型

C++11之auto类型推导

C++11之decltype类型推导(使用场景、推导四规则、cv限定符)

C++11之基于范围的for循环

C++11之强类型转换(枚举类)



运行时常量性与编译时常量性

常量,表示这个数值不可被修改。在C++11之前都是通过const修饰。const可以修饰函数参数函数返回值函数本体(常成员函数)变量等等。但在部分场景下,const表示的常量是运行时常量,只能保证运行时数据不会发生变化。这样就会导致我们需要在编译时的常量性不能给予体现。

例如,下面编写了一个函数GetSize()返回一个被const修饰的整数。这种被const修饰的就仅仅表示的是运行时常量。所以,在使用这个常量数值创建数组时编译失败,当作枚举成员赋值也编译失败,作为swicth中的case 分支也会编译错误。

#include <iostream>

using namespace std;

const int GetSize()
{
	return 1;
}

int main()
{
	int arr[GetSize()] = {0};

	enum MyEnum
	{
		A = GetSize(),
	};
	int x;
	switch (x)
	{
	case GetSize():
		break;
	default:
		break;
	}

	return 0;
}

编译报错:

Source.cpp(12,17): error C2131: expression did not evaluate to a constant
Source.cpp(12,17): message : failure was caused by call of undefined function or one not declared 'constexpr'
Source.cpp(12,17): message : see usage of 'GetSize'
Source.cpp(16,14): error C2131: expression did not evaluate to a constant
Source.cpp(16,14): message : failure was caused by call of undefined function or one not declared 'constexpr'
Source.cpp(16,14): message : see usage of 'GetSize'
Source.cpp(21,14): error C2131: expression did not evaluate to a constant
Source.cpp(21,14): message : failure was caused by call of undefined function or one not declared 'constexpr'
Source.cpp(21,14): message : see usage of 'GetSize'
Source.cpp(21,1): error C2051: case expression not constant
Source.cpp(25,2): warning C4065: switch statement contains 'default' but no 'case' labels

constexpr常量表达式(C++11)

上述这种场景我们可以通过#define进行处理,但是这种粗暴的方法在C++中有点不合群。为此C++11引入了constexpr,常量表达式(constant expression)。

还是聊刚刚那个例子,我们只需要将原本的const修饰改为constexpr修饰即可实现编译时常量

constexpr int GetSize()
{
	return 1;
}

常量表达式函数(constexpr)

虽然说我们只需要在函数返回类型前加入关键字constexpr就可以让函数变成常量表达式。但是常量表达式是有一定限制的。

具体要求如下四条:
1. 函数体只有单一的return返回语句
2. 函数必须要有返回值(不能为void)
3. 在使用前必须已有定义
4. return返回语句表达式中不能使用非常量表达式的函数、全局数据,并且必须是一个常量表达式。

最严格的要求毫无疑问就是函数体内只能有单一的return返回语句。例如下面这个Getsize函数中有一行输出就会导致constxpr修饰失败。如果是那种可以被优化掉的语句将不受影响。

constexpr int GetSize()
{
	cout << 1 << endl;
	return 1;
}

函数必须要有返回值其实能理解,因为constexpr修饰的就是返回值,然而没有返回值那么constexpr就没有意义。

在使用前必须已有定义,这个可能不太好理解。对于普通的函数只需要声明即可。而常量表达式函数的使用比较特殊,使用和调用并不一样。前者讲的是编译时值的计算,后者讲的是运行时函数的调用。

下面这段代码中,我们声明了常量表达式函数f()。在定义f函数前,我们定义了变量a、常量b、常量表达式cab的初始化中编译器会将f函数转换为一个函数调用。在c的初始化中,由于c是一个常量表达式,所以编译器进行编译时就会计算出函数的值。然而此时函数f并未定义,所以就会造成编译错误。最后d的初始化时,f函数已经定义过了。所以在编译期可以正常计算出值。

	constexpr int f();

	int a = f();

	const int b = f();

	constexpr int c = f(); // 编译错误

	constexpr int f()
	{
		return 1;
	}

	constexpr int d = f();

return语句必须是常量表达式。我们要使其返回值变成一个编译期的常量,而return语句的表达式中包含了运行时才能确定的值,这是互相矛盾的。

例如下面这个例子,返回的表达式中包含了参数就没法在编译期计算出值了。

constexpr GetSize(int z)
{
	return z;
}

常量表达式值

常量表达式值必须被一个常量表达式赋值。和常量表达式函数相同,常量表达式值在使用前必须初始化。C++11标准要求,编译时的浮点数常量表达式的精度必须大于等于运行时的浮点数常量的精度(为了解决浮点数精度丢失问题)。

使用const修饰与constexpr修饰变量有声明区别?

const int i = 1;
constexpr int j = 1;

分析:在大多数场景下俩种效果相同,而在全局作用域下 编译期一定会为常量i产生数据,对于常量j如果有代码使用它才会产生数据,否则仅仅当作编译期的值。

对于自定义类型的数据,要成为常量表达式值的话,还需要定义自定义常量构造函数(constant-expression constructor)

class T
{
public:

	constexpr T(int index)
		:i(index)
	{

	}

private:
	int i;
};




int main()
{
	constexpr T t(1);

	return 0;
}

常量表达式的构造函数条件:

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

例如下面代码,在创建常量表达式值时参数必须是编译期可得出的值。

	int xx = 1;
	constexpr T t(xx);

我们创建了这个T类型的对象,那么该类型的成员也将具有编译期的常量性。

需要注意一点就是常量表达式不能作用于virtual的成员函数中,virtual表示为运行时,constexpr表示为编译期, 二者是冲突的。
当我们在添加了常量表达式构造函数后就必须要添加非常量表达式构造函数的版本,因为常量表达式构造函数也可用于非常量表达式中的类型构造。

常量表达式的其他应用

1、常量表达式还可以用于模板函数,当由于模板的类型不确定性。所以C++11规定,当声明为常量表达式的模板后,某个该模板的实例化结果不满足常量表达式的要求后,constexpr会自动忽略。

2、 常量表达式还支持递归函数,C++11标准的编译器对常量表达式至少支持512层的递归。

C++模板元编程(template meta-programming)
可以在编译期计算递归函数的值。

template<long int num>
struct Fibonacci
{
	static const long int val = Fibonacci<num - 1>::val + Fibonacci<num - 2>::val;
};

template<> 
struct Fibonacci<2>
{
	static const long int val = 1;
};

template<>
struct Fibonacci<1>
{
	static const long int val = 1;
};


template<>
struct Fibonacci<0>
{
	static const long int val = 0;
};


int main()
{
	int fib[] = {
		Fibonacci<11>::val,Fibonacci<12>::val,
		Fibonacci<13>::val,Fibonacci<14>::val,
		Fibonacci<15>::val,Fibonacci<16>::val,
	};

	for (const auto elem : fib)
	{
		cout << elem << endl;
	}

	return 0;
}

那么用constexpr关键字将会极大的降低代码体积。


constexpr int Fibonacci(int n)
{
	return (1 == n) ? 1 : ((2 == n) ? 1 : Fibonacci(n - 1) + Fibonacci(n - 2));
}

int main()
{
	int fib[] = {
		Fibonacci(11), Fibonacci(22),
		Fibonacci(13), Fibonacci(24),
		Fibonacci(15), Fibonacci(26),
	};

	for (const auto elem : fib)
	{
		cout << elem << endl;
	}

	return 0;
}

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
C++11 引入了 constexpr 关键字,用于指定函数或对象是常量表达式常量表达式是在编译时就能计算出结果的表达式,它可以用于数组大小、模板参数等需要在编译时确定的地方。 constexpr 函数 constexpr 函数是指能在编译时求值的函数,它的返回值可以作为常量表达式使用。constexpr 函数的参数和返回值必须是字面类型。 例如,下面的函数就是一个 constexpr 函数: ```cpp constexpr int square(int x) { return x * x; } ``` 我们可以在编译时计算出 square(5) 的值,因此它是一个常量表达式constexpr 对象 constexpr 对象是指在编译时就能计算出值的对象。constexpr 对象必须被声明为 const,而且必须用常量表达式初始化。 例如,下面的语句定义了一个 constexpr 对象: ```cpp constexpr int max_num = 100; ``` 我们可以在编译时就知道 max_num 的值是 100,因此它是一个常量表达式constexpr 函数和常量表达式的限制 constexpr 函数和常量表达式有一些限制: 1. constexpr 函数必须有一个返回值,而且返回值必须是字面类型。 2. constexpr 函数的函数体必须足够简单,能在编译时被求值。 3. constexpr 函数不能包含任何副作用,比如修改全局变量或调用非 constexpr 函数。 4. constexpr 函数的参数和返回值必须是字面类型。 5. constexpr 对象必须被声明为 const,而且必须用常量表达式初始化。 6. constexpr 对象的类型必须是字面类型。 总结 constexpr 关键字用于指定函数或对象是常量表达式constexpr 函数和常量表达式必须在编译时就能计算出值,它们有一些限制。constexpr 函数和常量表达式可以用于数组大小、模板参数等需要在编译时确定的地方。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林夕07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值