前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >常量表达式是什么_const常量

常量表达式是什么_const常量

作者头像
全栈程序员站长
发布2022-11-10 14:23:10
7270
发布2022-11-10 14:23:10
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

常量表达式值(constant-expression value)。通常情况下,常量表达式值必须被一个常量表达式赋值,而跟常量表达式函数一样,常量表达式值在使用前必须被初始化。

一、常量表达式

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

在C++中,我们常常会遇到常量的概念。常量表示该值不可修改, 通常是通过const关键字来修饰的。比如:

代码语言:javascript
复制
const int i = 3;

const还可以修饰函数参数、函数返回值、函数本身、类等。在不同的使用条件下,const有不同 的意义,不过大多数情况下,const描述的都是一些“运行时常量性”的概念,即具有运行时数据的不可更改性。不过有的时候,我们需要的却是编译时期的常量性,这是const关键字无法保证的。

代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
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函数。

代码语言:javascript
复制
#define GetConst 1

C++11中对编译时期常量的回答是constexpr,即常量表达式(constant expression)。

代码语言:javascript
复制
constexpr int GetConst() { 
 return 1; }

即在函数表达式前加上constexpr关键字即可。有了常量表达式这样的声明,编译器就可以在编译时期对GetConst表达式进行值计算(evaluation),从而将其视为一个编译时期的常量(虽然编译器不一定 这么做,但至少从语法效果上看是这样,我们会在后面叙述)。

#define GetConst 1

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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语句。 这就意味着形如:

代码语言:javascript
复制
constexpr int data() { 
 const int i = 1; return i; }

这样的多条语句的写法是无法通过编译的。不过一些不会产生实际代码的语句在常量表达式函数中使用下,倒不会导致编译器的“抱怨”。我们可以看看如下static_assert的情况:

代码语言:javascript
复制
constexpr int f(int x){ 

static_assert(0 == 0, "assert fail.");
return x;
}

该例子能够通过编译。而其他的,比如using指令、typedef等也通常不会造成问题。

第二点约束,则是常量表达式必须返回值。形如constexpr void f(){}这样的不返回值的函数就不能是常量表达式。当然,其原因也很明显,因为无法获得常量的常量表达式是不被认可的。 第三点约束是常量表达式函数在使用前必须被定义。对于普通函数而言,调用函数只需要有函数声明就够了,但常量表达式函数的使用则有所不同。这里读者应该注意常量表达式“使用”和“调用”的区别,前者 讲的是编译时的值计算,而后者讲的是运行时的函数调用

代码语言:javascript
复制
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的定义已经有了。

第四点非常重要,常量表达式中,也不能使用非常量表达式的函数。形如

代码语言:javascript
复制
const int e(){ 
 return 1;}
constexpr int g(){ 
 return e(); }

或者形如

代码语言:javascript
复制
int g = 3;
constexpr int h() { 
 return g; }

的常量表达式定义是不能通过编译的。这样做的意义也比较明显,即如果我们要使得g()是一个编译时的常量,那么其return表达式语句就不能包含运行时才能确定返回值的函数。只有这样,编译器才能够在编译时进行常量表达式函数的值计算。 当然,作为一个常量表达式函数,return的表达式需要是一个常量表达式也是天经地义的事情。一些危险的操作,比如赋值的操作在常量表达式中也是不允许的,形如

代码语言:javascript
复制
constexpr int k(int x) { 
 return x = 1; }

的语句也是无法通过C++11编译器的编译的。

3.1 常量表达式值

常量表达式值(constant-expressionvalue)。通常情况下,常量表达式值必须被一个常量表达式赋值,而跟常量表达式函数一样,常量表达式值在使用前必须被初始化。而使用constexpr声明的数据最常被问起的问题是,下列两条语句有什么区别:

代码语言:javascript
复制
const int i = 1;
constexpr int j = 1;

事实上,两者在大多数情况下是没有区别的。不过有一点是肯定的,就是如果i在全局名字空间中,编译器一定会为i产生数据。而对于j,如果不是有代码显式地使用了它的地址,编译器可以选择不为它生成数据,而仅将其当做编译时期的值(是不是想起了光有名字没有产生数据的枚举值,以及不会产生数据的右值字面常量?事实上,它们也都只是编译时期的常量)。

而对于自定义类型的数据,要使其成为常量表达式值的话,则不像内置类型这么简单。C++11标准中,constexpr关键字是不能用于修饰自定义类型的定义的。比如下面这样的类型定义和使用:

代码语言:javascript
复制
constexpr struct MyType { 
int i; }
constexpr MyType mt = { 
0};

在C++11中,就是无法通过编译的。正确地做法是,定义自定义常量构造函数(constent-expression constructor)。

代码语言:javascript
复制
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的意义是冲突的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/188901.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月27日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、常量表达式
    • 1.1 运行时常量性与编译时常量性
      • 1.2 如何获得编译期常量
        • 2.1 常量表达式函数
          • 3.1 常量表达式值
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档