常量表达式值(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 constantenum { 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的意义是冲突的。

常量表达式值 constexpr相关推荐

  1. (P3-P4)constexpr修饰常量表达式和常量表达式函数

    文章目录 1. const 2.constexpr 3.常量表达式函数 1. const 在 C++11 之前只有 const 关键字,从功能上来说这个关键字有双重语义:变量只读,修饰常量 eg: v ...

  2. c语言 常量表达式,C++11 constexpr:验证是否为常量表达式(长篇神文)

    constexpr 是 C++ 11 标准新引入的关键字,不过在讲解其具体用法和功能之前,读者需要先搞清楚 C++ 常量表达式的含义. 所谓常量表达式,指的就是由多个(≥1)常量组成的表达式.换句话说 ...

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

    系列文章 C++11之正则表达式(regex_match.regex_search.regex_replace) C++11之线程库(Thread.Mutex.atomic.lock_guard.同步 ...

  4. C++11 nullptr与常量表达式constexpr记录

    1.nullptr (1) nullptr是一个关键字,而nullptr_t是一个类型 typedef decltype(nullptr) nullptr_t 使用nullptr_t类型必须包含#in ...

  5. [C++11]常量表达式函数

    constexpr修饰函数. 普通函数/类成员函数. 1.函数必须要有返回值,并且return返回的表达式必须是常量表达式. 代码如下: #include <iostream> using ...

  6. C++常量表达式函数

    目录 常量表达式的功能 constexpr关键字和用户定义型别 constexpr对象 constexpr函数 constexpr模版 整数字面值即为常量表达式(constant expression ...

  7. c++常量和常量表达式

    const,默认情况下仅在文件内有效 const int i(12); const引用:对常量的引用不能被用作修改它所绑定的对象 const int ci(5); const int &rci ...

  8. C++语言编程概念:常量、常量表达式和常量初始化

    常量 常量是固定值,在程序执行期间不会改变.这些固定的值,又叫做字面值.常量可以是任何的基本数据类型,可分为整型数字.浮点数字.字符.字符串和布尔值.常量就像是常规的变量,只不过常量的值在定义后不能进 ...

  9. 初识C++ - 常量表达式函数

    常量表达式函数(constexpr function)是指能用于常量表达式的函数.其定义的方法和其他函数差不多,但主要满足一下规则: 1. 函数返回类型是字面值类型 2. 函数参数是字面值类型 3. ...

最新文章

  1. 太阳电池板特性实验_汕头市通风柜厂家报价-广州中增实验室设备
  2. pid控制从入门到精通pdf_【应用指南】PID调节让流量/压力控制又快又稳
  3. List-----Array
  4. 语言运行泰博那契数列_波浪理论的数字基础-斐波那契数列
  5. ABAP 对字符串公式进行计算
  6. 乐橙本地录像回放不了_本地工具访问:安全、高效、合规的IT资源远程访问
  7. python如何查询数据库_python如何实现查询sql数据库并生成html文件?
  8. js检查元素是否包括在数组中
  9. 可编译的java代码_有没有编译方法可以运行Java代码?
  10. win10怎么打开计算机树形,win10系统中显示树形目录文件夹的两种方法
  11. UAS:大众点评用户行为系统
  12. nlp基础—12.LSTM-CRF模型介绍
  13. MF前传——探索者一号液晶屏接线
  14. 水星怎么设置网速最快_水星路由器怎么设置网速最快
  15. 【中级软考—软件设计师】2操作系统2.6段页式存储【**】:2.6.1页式存储
  16. 0-1背包问题和部分背包(fractional knapsack)问题分析(动态规划,贪心算法)
  17. 学习LSSVM以及区别LSSVM和SVM看的几篇博文
  18. 2190 悼念512汶川大地震遇难同胞——重建希望小学
  19. 21考研:一研为定,定为研一
  20. 【安全资讯】东京奥运会现钓鱼网站已有观众受骗

热门文章

  1. pycharm如何添加文件注释和函数注释
  2. linux查看串口波特率
  3. ZYNQ_Standalone_SD卡读写与性能测试
  4. C# Task任务队列
  5. java写数据进mysql_java怎样将读取数据写入数据库
  6. 计算机专业 英语口语,计算机专业英语口语翻译-重要控制键
  7. 医学科研课题设计的分类
  8. C# 用NPOI将DataGridView中显示的数据导出到Excel(.xls和.xlsx格式)
  9. 遇见,只是一个开始;守望,才能相伴一生
  10. 面试造火箭,工作拧螺丝?看下这些大厂原题吧(iOS开发方向)