首页 > 编程语言 >一文总结 C++ 常量表达式、constexpr 和 const

一文总结 C++ 常量表达式、constexpr 和 const

时间:2024-02-19 21:23:16浏览次数:27  
标签:const 常量 编译 C++ constexpr 表达式 函数

TLDR

  1. 修饰变量的时候,可以把 constexpr 对象当作加强版的 const 对象:const 对象表明值不会改变,但不一定能够在编译期取得结果;constexpr 对象不仅值不会改变,而且保证能够在编译期取得结果。如果一个 const 变量能够在编译期求值,将其改为 constexpr 能够让代码更清晰易读。
  2. constexpr 函数可以把运行期计算迁移至编译期,使得程序运行更快(但会增加编译时间)。但如果 constexpr 函数中存在无法在编译期求值的参数,则 constexpr 函数和普通一样在运行时求值,此时的返回值不是常量表达式。

1. 常量表达式和 constexpr

C++11 中引入了 constexpr 关键字。constexpr 是 const expression 的缩写,即常量表达式

常量表达式是指值不会改变编译期可以得到结果的表达式。

1.1 特点

  1. 值不会改变(这一点和普通 const 一样)
  2. 编译期就能得到结果!(普通 const 不一定保证)

1.2 使用场景

C++ 在一些场景下必须使用常量表达式,比如:

  • 数组大小
  • 整型模板实参(如 std::array<T, N> 的长度参数 N)
  • switch-case 中的 case 标签
  • 枚举量的值
  • 对齐规格

1.3 常见的常量表达式

  1. 字面值(如 42)
  2. 用常量表达式初始化的 const 对象

一个对象(或表达式)是否是常量表达式取决于类型和初始值,如:

int i1 = 42;           // i1 不是常量表达式:初始值 42 是字面值,但 i1 不是 const 类型
const int i2 = i1;     // i2 不是常量表达式:初始值 i1 不是常量表达式
const int i3 = 42;     // i3 是常量表达式:用字面值 42 初始化的 const 对象
const int i4 = i3 + 1; // i4 是常量表达式:用常量表达式 i3 + 1 初始化的 const 对象
const int i5 = getValue(); // 如果 getValue() 是普通函数,则 i5 值要到运行时才能确定,则不是常量表达式

1.4 constexpr 变量

上面的例子可以看出,不能直接判断一个 const 对象是否是常量表达式:例如 i4 是否是常量表达式取决于 i3 是否是常量表达式,而 i4 又可能用来初始化其他常量表达式。在复杂的系统中,很难一眼看出某个 const 对象是否是常量表达式。

C++11 允许把变量声明为 constexpr 类型,此时编译器会保证 constexpr 变量是常量表达式(否则编译报错)。换句话说,只要看到 constexpr 类型的变量,则一定能够在编译期取得结果,可以用在需要常量表达式的场景。

int i1 = 42;
constexpr int i2 = i1; // constexpr 变量 'i2' 必须由常量表达式初始化。不允许在常量表达式中读取非 const 变量 'i1'
constexpr int i3 = 42; // i3 是常量表达式
constexpr int i4 = i3 + 1; // i4 是常量表达式
constexpr int i5 = getValue(); // 只有 getValue() 是 constexpr 函数时才可以,否则编译报错

1.5 constexpr 函数

constexpr 函数是指能用于常量表达式的函数。

需要强调的是,constexpr 函数既能用于要求常量表达式/编译期常量的语境,也可以作为普通函数使用

注意:constexpr 函数不一定返回常量表达式!

只有 constexpr 的所有实参都是常量表达式/编译期常量时,constexpr 函数的结果才是常量表达式/编译期常量。只要有一个参数在编译期未知,那就和普通函数一样,在运行时计算。

constexpr int sum(int a, int b) {
  return a + b;
}

constexpr int i1 = 42;
constexpr int i2 = sum(i1, 52); // 所有参数都是常量表达式,sum 的结果也是常量表达式,在编译期求值

int AddThree(int i) {
  return sum(i, 3); // i 不是常量表达式,此时 sum 作为普通函数使用
}

为了能保证 constexpr 函数在编译时能随时展开计算,constexpr 函数隐式内联。内联函数和 constexpr 函数不同于其他函数,允许定义多次,但要保证所有的定义一致。正因如此,内联函数和 constexpr 函数一般定义在头文件中

constexpr 限制

因为需要在编译期求值,所以 constexpr 函数有一些限制:返回类型和所有形参的类型必须是字面值类型(literal type)。除了内置类型,用户自定义的类也可以是字面值类型,因为它的构造函数和成员函数也可以是 constexpr 函数。

C++11 中 constexpr 函数还有一些额外限制(C++14 没有这些限制):

  • 返回值类型不能是 void
  • 函数体内只能有且只有一条 return 语句(但可以用 ? : 三目运算符和递归)
  • 如果是类的成员函数,则为隐式 const 成员函数

1.6 使用 constexpr 的好处

  1. 编译器可以保证 constexpr 对象是常量表达式(能够在编译期取得结果),而 const 对象不能保证。如果一个 const 变量能够在编译期求值,将其改为 constexpr 能够让代码更清晰易读
  2. constexpr 函数可以把运行期计算迁移至编译期,使得程序运行更快(但会增加编译时间)

对于常量表达式(编译期值已知),编译器可以进行更多优化,比如放到只读内存中。但这并不是 constexpr 特有的,有的 const 变量也是常量表达式

1.7 小结

  1. 修饰对象的时候,可以把 constexpr 当作加强版的 const:const 对象只表明值不会改变,不一定能够在编译期取得结果;constexpr 对象不仅值不会改变,而且保证能够在编译期取得结果
  2. constexpr 函数既可以用于编译期计算,也可以作为普通函数在运行期使用

扩展阅读

  • 《C++ Primer 第五版》p58,p214,p267

  • 《Effective Modern C++》条款 15:只要有可能使用 constexpr,就使用它

标签:const,常量,编译,C++,constexpr,表达式,函数
From: https://www.cnblogs.com/tengzijian/p/18018104

相关文章

  • Qt error: LNK2001: 无法解析的外部符号 “public: virtual struct QMetaObject const
    简介  通过QtCreator开发一个动态库,最开始希望只是一个简单的纯C++的动态库,就没有继承QObject,也没有写Q_OBJECT宏。编译时就会报:LNK2001:无法解析的外部符号public:virtualstructQMetaObjectconst。绕了不少弯子,终于解决了,把原因分析进行个整理。面试有个问题:Qt信号槽机......
  • Qt error: LNK2001: 无法解析的外部符号 "public: virtual struct QMetaObject const
    这个问题总是在编译的不经意间出现,而且一出一大片,很烦。作为新手出了问题可定要在网上找答案,但是总是发现别人的解决方法解决不了自己的问题,唉~在这个问题上大家大家提出的大多数是.h.cpp文件不对应、.h中声明的文件在.cpp文件中没有实现、函数声明在了.cpp文件中等等一......
  • C++ 深拷贝浅拷贝
    C++深拷贝浅拷贝C++默认生成的拷贝构造函数,他的行为就是浅拷贝,他只会复制一个一模一样的的指针,并不会操作指针指向的东西。要想实现我们的逻辑需求,就要自定义拷贝构造函数,实现深拷贝。我们来具体说明一下上面的话首先我们创建一个简单的类#include<iostream>usingnamespa......
  • 线性插值计算百分位数的C++示例
    代码如下#include<iostream>#include<vector>#include<algorithm>doublepercentile_linear_interpolation(conststd::vector<double>&data,doublepercentile){//确保百分位数在合理范围内if(percentile<0.0||percentile>1......
  • C/C++ 宏区分不同系统、编译器、语言版本
    目录区分不同系统区分不同编译器及其版本区分不同语言及其版本参考区分不同系统1)_WIN32,Windows系统_WIN64:32bitand64bit系统M_WIN64:仅64bitWindows系统M_WIN32:仅32bitWindows系统_WINDOWS:GUIApplication_CONSOLE:consoleApplication2)__APPLE__,苹果系统,包括MAC、IOST......
  • VC++ 中 CT2A CA2T 两个宏进行字符串转换简单测试
    #include"afxwin.h"#include<iostream>usingnamespacestd;intmain(){CStringcs=_T("西游记");AfxMessageBox(_T("CString:")+cs);//CString转ACSIICT2Aa_str(cs);stringstd_str(a_str);......
  • KY78 最大上升子序列和C++
    这个解决问题的思路使用动态规划,即用已知状态去得到未知状态。思路逻辑是这样sum[i]记录以A[i]为末上升子序列的和的最大值然后从j从0-i-1遍历如果A[j]<A[i]那么sum[i]=sum[j]+A[i];然后找出sum[i]中的的最大值,就是以A[i]为末上升子序列的和的最大值。这样就实现了从前......
  • FUN GAME 一款普通的C++游戏
    凑合看吧,不是完整版。#include<bits/stdc++.h>#include<windows.h>#include<conio.h>usingnamespacestd;#defineptputs#definepfprintf#definepcputchar#definesfscanf#definegtgets#defineslSleepcharname[101];stack<int>gun;bo......
  • 15. C++类中成员变量的初始化总结
    C++类中成员变量的初始化总结1.普通的变量:一般不考虑啥效率的情况下可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。classCA{public:intdata;public:CA();};/*********/CA::CA():data(0)//……#1……初始化列表方式{......
  • KY148 还是畅通工程C++
    求图的最小生成树。克鲁斯卡尔算法来解决。就是选择n-1条最小边且无回路。回路判断用并查集就行。即要加入的边(两个节点)具有相同的父节点说明如果这两个节点本来就存在路径,再加入一条边就会产生回路,舍去。#include<iostream>#include<algorithm>usingnamespacestd;struc......