大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE稳定放心使用
常量表达式值(constant-expression value)。通常情况下,常量表达式值必须被一个常量表达式赋值,而跟常量表达式函数一样,常量表达式值在使用前必须被初始化。
一、常量表达式
1.1 运行时常量性与编译时常量性
在C++中,我们常常会遇到常量的概念。常量表示该值不可修改,
通常是通过const关键字来修饰的。比如:
const int i = 3;
const还可以修饰函数参数、函数返回值、函数本身、类等。在不同的使用条件下,const有不同
的意义,不过大多数情况下,const描述的都是一些“运行时常量性”的概念,即具有运行时数据的不可更改性。不过有的时候,我们需要的却是编译时期的常量性,这是const关键字无法保证的。
#include <iostream>
#include <string>
using namespace std;
const int GetConst() {
return 1; }
void Constless(int cond) {
int arr[GetConst()] = {
0}; // 无法通过编译
enum {
e1 = GetConst(), e2 }; // 无法通过编译
switch (cond) {
case GetConst(): // 无法通过编译
break;
default:
break;
}
}
int main()
{
cout<< GetConst()<< endl;
}
bash-4.4$ g++ constexpr.cpp -o expr && ./expr
constexpr.cpp: In function ‘void Constless(int)’:
constexpr.cpp:8:21: error: call to non-‘constexpr’ function ‘const int GetConst()’
enum {
e1 = GetConst(), e2 }; // 无法通过编译
~~~~~~~~^~
constexpr.cpp:8:21: error: call to non-‘constexpr’ function ‘const int GetConst()’
constexpr.cpp:8:22: error: enumerator value for ‘e1’ is not an integer constant
enum {
e1 = GetConst(), e2 }; // 无法通过编译
^
constexpr.cpp:10:14: error: call to non-‘constexpr’ function ‘const int GetConst()’
case GetConst(): // 无法通过编译
~~~~~~~~^~
constexpr.cpp:10:14: error: call to non-‘constexpr’ function ‘const int GetConst()’
我们定义了一个返回常数1的函数GetConst。我们使用了const关键字修饰了返回类型。不过编译后我们发现,无论将GetConst的结果用于需要初始化数组Arr的声明中,还是用于匿名枚举中,或用于switch-case的case表达式中,编译器都会报告错误。发生这样错误的原因如我们上面提到的一样,这些语句都需要的是编译时期的常量值。而const修饰的函数返回值,只保证了在运行时期内其值是不可以被更改的。这是两个完全不同的概念。
1.2 如何获得编译期常量
简单粗暴的做法 :使用C中的宏替代GetConst函数。
#define GetConst 1
C++11中对编译时期常量的回答是constexpr,即常量表达式(constant expression)。
constexpr int GetConst() {
return 1; }
即在函数表达式前加上constexpr关键字即可。有了常量表达式这样的声明,编译器就可以在编译时期对GetConst表达式进行值计算(evaluation),从而将其视为一个编译时期的常量(虽然编译器不一定
这么做,但至少从语法效果上看是这样,我们会在后面叙述)。
#define GetConst 1
#include <iostream>
#include <string>
using namespace std;
#define GetConst 1
//const int GetConst() { return 1; }
void Constless(int cond) {
int arr[GetConst] = {
0};
enum {
e1 = GetConst, e2 };
switch (cond) {
case GetConst:
cout<< GetConst <<endl;
break;
default:
break;
}
}
int main()
{
//cout<< GetConst()<< endl;
Constless(1);
}
constexpr
#include <iostream>
#include <string>
using namespace std;
constexpr int GetConst() {
return 1; }
void Constless(int cond) {
int arr[GetConst()] = {
0};
enum {
e1 = GetConst(), e2 };
switch (cond) {
case GetConst():
break;
default:
break;
}
}
int main()
{
cout<< GetConst()<< endl;
}
2.1 常量表达式函数
通常我们可以在函数返回类型前加入关键字constexpr来使其成为常量表达式函数。不过并非所有的函数都有资格成为常量表达式函数。事实上,常量表达式函数的要求非常严格,总结起来,大概有以下几点:
·函数体只有单一的return返回语句。
·函数必须返回值(不能是void函数)。
·在使用前必须已有定义。
·return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。
首先是常量表达式函数中最为明显的限制,就是要求函数体中只有一条语句,且该条语句必须是return语句。
这就意味着形如:
constexpr int data() {
const int i = 1; return i; }
这样的多条语句的写法是无法通过编译的。不过一些不会产生实际代码的语句在常量表达式函数中使用下,倒不会导致编译器的“抱怨”。我们可以看看如下static_assert的情况:
constexpr int f(int x){
static_assert(0 == 0, "assert fail.");
return x;
}
该例子能够通过编译。而其他的,比如using指令、typedef等也通常不会造成问题。
第二点约束,则是常量表达式必须返回值。形如constexpr void f(){}这样的不返回值的函数就不能是常量表达式。当然,其原因也很明显,因为无法获得常量的常量表达式是不被认可的。
第三点约束是常量表达式函数在使用前必须被定义。对于普通函数而言,调用函数只需要有函数声明就够了,但常量表达式函数的使用则有所不同。这里读者应该注意常量表达式“使用”和“调用”的区别,前者
讲的是编译时的值计算,而后者讲的是运行时的函数调用
constexpr int f();
int a = f();
const int b = f();
constexpr int c = f(); //无法通过编译 f先声明,此时还没有实现
constexpr int f() {
return 1; }
constexpr int d = f();
在a和b的定义中,编译器会将f()转换为一个函数调用,
而在c的定义中,由于其是一个常量表达式值,因此会要求编译器进行编译时的值计算。
这时候由于f常量表达式还没有定义,就会导致编译错误。
而d的定义则没有问题,因为f的定义已经有了。
第四点非常重要,常量表达式中,也不能使用非常量表达式的函数。形如
const int e(){
return 1;}
constexpr int g(){
return e(); }
或者形如
int g = 3;
constexpr int h() {
return g; }
的常量表达式定义是不能通过编译的。这样做的意义也比较明显,即如果我们要使得g()是一个编译时的常量,那么其return表达式语句就不能包含运行时才能确定返回值的函数。只有这样,编译器才能够在编译时进行常量表达式函数的值计算。
当然,作为一个常量表达式函数,return的表达式需要是一个常量表达式也是天经地义的事情。一些危险的操作,比如赋值的操作在常量表达式中也是不允许的,形如
constexpr int k(int x) {
return x = 1; }
的语句也是无法通过C++11编译器的编译的。
3.1 常量表达式值
常量表达式值(constant-expressionvalue)。通常情况下,常量表达式值必须被一个常量表达式赋值,而跟常量表达式函数一样,常量表达式值在使用前必须被初始化。而使用constexpr声明的数据最常被问起的问题是,下列两条语句有什么区别:
const int i = 1;
constexpr int j = 1;
事实上,两者在大多数情况下是没有区别的。不过有一点是肯定的,就是如果i在全局名字空间中,编译器一定会为i产生数据。而对于j,如果不是有代码显式地使用了它的地址,编译器可以选择不为它生成数据,而仅将其当做编译时期的值(是不是想起了光有名字没有产生数据的枚举值,以及不会产生数据的右值字面常量?事实上,它们也都只是编译时期的常量)。
而对于自定义类型的数据,要使其成为常量表达式值的话,则不像内置类型这么简单。C++11标准中,constexpr关键字是不能用于修饰自定义类型的定义的。比如下面这样的类型定义和使用:
constexpr struct MyType {
int i; }
constexpr MyType mt = {
0};
在C++11中,就是无法通过编译的。正确地做法是,定义自定义常量构造函数(constent-expression constructor)。
struct MyType {
constexpr MyType(int x): i(x){
}
int i;
};
constexpr MyType mt = {
0};
代码清单6-4中,我们对MyType的构造函数进行了定义。不过在定
义前,我们加上了constexpr关键字。通过这样的定义,MyType类型的
constexpr的变量mt的定义就可以通过编译了。
常量表达式的构造函数也有使用上的约束,主要的有以下两点:
·函数体必须为空。
·初始化列表只能由常量表达式来赋值。
在C++11中,不允许常量表达式作用于virtual的成员函数。这个原因也是显而易见的,
virtual表示的是运行时的行为,与“可以在编译时进行值计算”的constexpr的意义是冲突的。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/188901.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...