【C++ 学习 ㉙】- 详解 C++11 的 constexpr 和 decltype 关键字
目录
一、constexpr 关键字
1.1 - constexpr 修饰普通变量
1.2 - constexpr 修饰函数
1.3 - constexpr 修饰类的构造函数
1.4 - constexpr 和 const 的区别
二、decltype 关键字
2.1 - 推导规则
2.2 - 实际应用
一、constexpr 关键字
constexpr 是 C++11 新引入的关键字,不过在理解其具有用法和功能之前,我们需要先理解 C++ 常量表达式。
所谓常量表达式,指的是由多个(>= 1)常量组成的表达式,换句话说,如果表达式中的成员都是常量,那么该表达式就是一个常量表达式,这也意味着,常量表达式一旦确定,其值将无法修改。
实际开发中,我们经常用到常量表达式,以定义数组为例,数组的长度就必须是一个常量表达式:
int arr1[5] = { 0, 1, 2, 3, 4 }; // ok
int arr2[2 * 5] = { 0 }; // ok
// int len = 10;
// int arr3[len] = { 0 }; // error
我们知道,C++ 程序从编写完毕到执行分为四个阶段:预处理、编译、汇编和链接,得到可执行程序后就可以运行了。值得一提的是,常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果;而常量表达式的计算往往发生在程序的编译阶段,这可以大大地提高程序的执行效率, 因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都要计算一次的时间。
对于用 C++ 编写的程序,性能往往是永恒的追求,那么在实际开发中,如何才能判断一个表达式是否为常量表达式,进而获得在编译阶段即可执行的 "特权" 呢?除了人为判定外,还有我们一开始所提到的 C++11 新引入的 constexpr 关键字 。
constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。在 C++11 中,constexpr 可用于修饰普通变量、函数(包括普通函数、类的成员函数以及模板函数)以及类的构造函数。
注意:获得在程序编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被执行,具体的计算时机还是编译器说了算。
1.1 - constexpr 修饰普通变量
C++11 中,定义普通变量时可以用 constexpr 修饰,从而使该变量获得在编译阶段即可计算出结果的能力。
注意:使用 constexpr 修饰普通变量时,变量必须经过初始化且初始值必须是一个常量表达式。
constexpr int len = 10;
int arr[len] = { 0 }; // ok
在此示例中,也可以将 constexpr 替换成 const,即:
const int len = 10;
int arr[len] = { 0 }; // ok
注意:const 和 constexpr 并不相同,关于它们的区别, 后面会进行详解。
1.2 - constexpr 修饰函数
constexpr 还可以用于修饰函数的返回值,这样的函数又称为 "常量表达式函数"。
注意:constexpr 并非可以修饰任意函数的返回值,换句话说,一个函数要想成为常量表达式,必须满足如下三个条件:
-
函数必须有返回值,即函数的返回值类型不能是 void。
constexpr void func() { } // 函数的返回值类型不能是 void
-
整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言以外,只能包含一条 return 返回语句,且 return 返回的表达式必须是常量表达式。
constexpr int func(int x) {constexpr int y = 0; // 函数体中只能包含一条 return 返回语句return 1 + 2 + x + y; }
int y = 0; constexpr int func(int x) {return 1 + 2 + x + y; // return 返回的表达式必须是常量表达式 }
#include <iostream> using namespace std; constexpr int y = 0; constexpr int func(int x) {return 1 + 2 + x; } int main() {int arr[func(3)] = { 0 };cout << sizeof(arr) << endl;return 0; }
-
函数在使用之前,必须有对应的定义语义。普通函数的调用只需要提前写好该函数的声明部分即可,函数的定义部分可以放在调用位置之后甚至其他文件中,但常量表达式函数在使用前,必须要有该函数的定义。
#include <iostream> using namespace std; constexpr int func(int x); int main() {int arr[func(3)] = { 0 };cout << sizeof(arr) << endl;return 0; } constexpr int func(int x) {return 1 + 2 + x; }
以上三个条件不仅对普通函数适用,对类的成员函数和模板函数也适用。
但由于函数模板中的类型不确定,因此实例化后的模板函数是否符合常量表达式函数的要求也是不确定的,针对这种情况,C++11 规定:如果 constexpr 修饰的实例化后的模板函数不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。
1.3 - constexpr 修饰类的构造函数
如果想直接得到一个常量对象,也可以使用 constexpr 修饰一个构造函数,这样就可以得到一个常量构造函数。常量构造函数有一个要求:构造函数的函数体必须为空,且必须采用初始化列表的方式为各个成员赋值。
#include <iostream>
using namespace std;
struct Person
{const char* _name;int _age;
constexpr Person(const char* name, int age): _name(name), _age(age){ }
};
int main()
{constexpr Person p{ "张三", 18 };cout << p._name << ":" << p._age << endl; // 张三:18return 0;
}
1.4 - constexpr 和 const 的区别
在 C++11 之前只有 const 关键字,其在实际使用中经常会表现出两种不同的语义。
void func(const int num)
{// int arr1[num] = { 0 }; // error(num 是一个只读变量,而不是常量)const int count = 5;int arr2[count] = { 0 }; // ok(count 是一个常量)
}
func 函数的参数 num 是一个只读变量,其本质上仍然是变量,而不是常量。
注意:只读并不意味着不能被修改,两者之间没有必然的联系,例如:
#include <iostream> using namespace std; int main() {int a = 520;const int& ra = a;a = 1314;cout << ra << endl; // 1314return 0; }
引用 ra 是只读的,即无法通过自身去改变自己的值,但并不意味着无法通过其他方式间接去改变,通过改变 a 的值就可以改变 ra 的值。
func 函数体中的 count 则被看成是一个常量,所以可以用来定义一个静态数组。
const int count = 5; int* ptr = (int*)&count; *ptr = 10; cout << count << endl;
为什么输出的 count 和 *ptr 不同呢?
具体原因是 C++ 中的常量折叠(或者常量替换):将 const 常量放在符号表中,给其分配内存,但实际读取时类似于宏替换。
为了解决 const 关键字的双重语义问题,C++11 引入了新的关键字 constexpr,建议凡是表达 "只读" 语义的场景都使用 const,凡是表达 "常量" 语义的场景都使用 constexpr。
所以在上面的例子中,在 func 函数体中使用 const int count = 5;
是不规范的,应使用 constexpr int count = 5;
。
二、decltype 关键字
decltype 是 C++11 新增的一个关键字,它和 auto 一样,都用来在编译期间进行自动类型推导。
decltype 是 "declare type" 的缩写,即 "声明类型"。
既然有了 auto,为什么还需要 decltype 呢?因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下,auto 用起来非常不方便,甚至压根无法使用,所以 decltype 被引入到 C++11 中。
auto 和 decltype 的语法格式:
auto varname = value; // varname 表示变量名,value 表示赋给变量的值
decltype(exp) varname[ = value;] // exp 表示一个表达式
auto 根据 = 右边的初始值 value 推导出变量的类型,所以使用 auto 声明的变量必须初始化;而 decltype 根据 exp 表达式推导出变量的类型,跟 = 右边的初始值 value 没有关系,所以不要求初始化。
示例:
#include <iostream>
using namespace std;
int main()
{int x = 0;
decltype(x) y = 1;decltype(x + 3.14) z = 5.5;decltype(&x) ptr;
cout << typeid(y).name() << endl; // intcout << typeid(z).name() << endl; // doublecout << typeid(ptr).name() << endl; // int *
// 注意:// decltype 的推导是在编译期间完成的,// 它只是用于表达式类型的推导,并不会计算表达式的值decltype(x++) i;cout << x << endl; // 0return 0;
}
2.1 - 推导规则
当程序员使用 decltype(exp)
获取类型时,编译器将根据以下三条规则得出结果:
-
如果表达式为普通变量、普通表达式或者类成员访问表达式,那么
decltype(exp)
的类型就和表达式的类型一致。#include <iostream> using namespace std; class Test { public:string _str;static int _i; }; int Test::_i = 0; int main() {int x = 0;int& r = x;decltype(x) y = x; // y 被推导为 int 类型decltype(r) z = x; // z 被推导为 int& 类型++z;cout << x << " " << r << " "<< y << " " << z << endl; // 1 1 0 1 Test t;decltype(t._str) s = "hello world"; // s 被推导为 string 类型decltype(Test::_i) j = 10; // j 被推导为 int 类型return 0; }
-
如果表达式是函数调用,那么
decltype(exp)
的类型和函数返回值一致。#include <iostream> using namespace std; // 函数声明 int func_int(); int& func_int_r(); const int func_c_int(); const int& func_c_int_r(); int main() {int x = 0; decltype(func_int()) y = x; // y 被推导为 int 类型decltype(func_int_r()) z = x; // z 被推导为 int& 类型++z;cout << x << " " << y << " " << z << endl; // 1 0 1 decltype(func_c_int()) m = x; // m 被推导为 int 类型++m;cout << x << " " << y << " " << z << " " << m << endl; // 1 0 1 2 decltype(func_c_int_r()) n = x; // n 被推导为 const int& 类型return 0; }
注意:函数
func_c_int()
的返回值是一个纯右值(即在表达式执行结束后不再存在的数据,也就是临时性的数据),对于纯右值而言,只有类类型可以携带 const、volatile 限定符,除此之外需要忽略这两个限定符,因此 m 被推导为 int 类型,而不是 const int 类型。 -
如果表达式是一个左值、或者被括号 () 包围,那么
decltype(exp)
的类型就是表达式类型的引用,即假设 exp 的类型为 T,那么decltype(exp)
的类型就是 T&。#include <iostream> using namespace std; int main() {int x = 0;decltype((x)) y = x; // y 被推导为 int&++y;cout << x << " " << y << endl; // 1 1 decltype(x = x + 1) z = x; // z 被推导为 int&++z;cout << x << " " << y << " " << z << endl; // 2 2 2return 0; }
2.2 - 实际应用
decltype 的应用多出现在泛型编程中。
#include <vector>
using namespace std;
template<class T>
class Test
{
public:void func(T& container){_it = container.begin();// do something ... ...}
private:decltype(T().begin()) _it;// 当 T 是普通容器,_it 为 T::iterator;// 当 T 是 const 容器,_it 为 T::const_iterator。
};
int main()
{vector<int> v; Test<vector<int>> t1;t1.func(v);
const vector<int> v2;Test<const vector<int>> t2;t2.func(v2);return 0;
}
相关文章:

【C++ 学习 ㉙】- 详解 C++11 的 constexpr 和 decltype 关键字
目录 一、constexpr 关键字 1.1 - constexpr 修饰普通变量 1.2 - constexpr 修饰函数 1.3 - constexpr 修饰类的构造函数 1.4 - constexpr 和 const 的区别 二、decltype 关键字 2.1 - 推导规则 2.2 - 实际应用 一、constexpr 关键字 constexpr 是 C11 新引入的关键字…...

js获取视频编码
一.背景 有些浏览器不支持某些视频的编码方式导致播放出现问题,这个时候要限制视频上传 二.插件 https://unpkg.com/mediainfo.js0.1.4/dist/mediainfo.min.js 三.完整html代码 <!DOCTYPE html> <html lang"en"> <head><meta ch…...
560. 和为 K 的子数组 --力扣 --JAVA
题目 给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。 子数组是数组中元素的连续非空序列。 解题思路 数组项累加可以使用双层循环进行遍历;子数组的长度是不确定的,也可能存在1 1 2和1 1 - 1…...

【趣味随笔】农业机器人的种类与发展前景
📢:如果你也对机器人、人工智能感兴趣,看来我们志同道合✨ 📢:不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 📢:文章若有幸对你有帮助,可点赞 👍…...

使用CountdownLatch和线程池批量处理http请求,并处理响应数据
背景和问题 背景:最近项目的一个接口数据,需要去请求其他多个服务器的数据,然后统一返回; 问题点:如果遍历所有的服务器地址,然后串行请求就会出现请求时间过长,加入需要请求十个服务器&…...

记录--怎么写一个可以鼠标控制旋转的div?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 说在前面 鼠标控制元素旋转在现在也是一个很常见的功能,让我们从实现div元素的旋转控制开始来了解元素旋转的具体原理和实现方法吧。 效果展示 体验地址 code.juejin.cn/pen/7290719… 实现…...
JVM第十八讲:调试排错 - Java 问题排查之工具单
调试排错 - Java 问题排查之工具单 程序员想要有更好的发展,排查问题的能力一定得加强。举个例子:cpu100% 怎么排查,线上接口逐渐变慢了该怎么排查?慢查询该如何治理?你的思路是啥?本文是JVM第十八讲&#…...
JAVA基础-正则表达式(12)
目录 Java 正则表达式正则表达式实例正则表达式语法 Matcher 类的方法索引方法查找方法替换方法start 和 end 方法 Java 正则表达式 正则表达式定义了字符串的模式。 正则表达式可以用来搜索、编辑或处理文本。 正则表达式并不仅限于某一种语言,但是在每种语言中有细…...
[论文笔记]GPT-1
引言 今天带来论文Improving Language Understanding by Generative Pre-Training的笔记,它的中文题目为:通过生成式预训练改进语言理解。其实就是GPT的论文。 自然语言理解可以应用于大量NLP任务上,比如文本蕴含、问答、语义相似和文档分类。虽然无标签文本语料是丰富的,…...
【3D 图像分割】基于 Pytorch 的 VNet 3D 图像分割1(综述篇)
在上一个关于3D 目标的任务,是基于普通CNN网络的3D分类任务。在这个任务中,分类数据采用的是CT结节的LIDC-IDRI数据集,其中对结节的良恶性、毛刺、分叶征等等特征进行了各自的等级分类。感兴趣的可以直接点击下方的链接,直达学习&…...

css之Flex弹性布局
文章目录 🐕前言:🏨定义flex容器 display:flex🏨在flex容器中子组件进行排列🪂行排列 flex-direction: row🪂将行排列进行翻转排列 flex-direction: row-reverse🏅按列排列 flex-direction: col…...
web.xml配置详解
在Java Web应用程序中,web.xml是一个XML配置文件,用于定义和配置Servlet、过滤器、监听器和其他Web应用程序组件的行为和属性。web.xml文件通常位于Web应用程序的WEB-INF目录下,用于描述Web应用程序的部署信息和配置。以下是一些web.xml配置的…...
关于我学习Go语言在CSDN分享的心得体会
最近我一直在学习Go语言,并通过CSDN平台分享我的学习心得和体会。在这篇博客中,我将与大家分享我在学习Go语言过程中的经验和收获。希望通过这篇博客能够帮助其他Go语言初学者更好地掌握这门语言,并与广大Go语言爱好者进行交流和互动。 选择…...
Java类的Builder应用以及使用@Data和@Builder高效应用Builder
⭐Java Builder模式:是Java设计模式之一,它属于对象创建型模式,是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 结论一:使用lombok的Data和Builder注解构建Java类的Builder简洁高效&am…...

【Qt控件之QTabWidget】介绍及使用
描述 QTabWidget类提供了一个带有选项卡的小部件堆栈。 选项卡小部件提供了一个选项卡栏(参见QTabBar)和一个“页面区域”,用于显示与每个选项卡相关联的页面。默认情况下,选项卡栏显示在页面区域的上方,但可以使用…...

Linux实战——网络连接模式的三种模式
Linux可以分为三种网络模式: 桥接模式 (vmnet0) 仅主机模式 (vmnet1) NAT模式 (vmnet8) 当我们下载了vmware之后,在电脑会出现两个虚拟网卡,VMware Network Adapter VMnet1、VMware Network Adapter VMnet8。 可以通过查找 控…...

嵌入式实时操作系统的设计与开发(任意大小的内存管理)
任意大小的内存管理是根据用户需要为其分配内存,即用户需要多大内存就通过acoral_malloc2()为之分配多大内存,同时每块分配出去的内存前面都有一个控制块,控制块里记录了该块内存的大小。 同时未分配出去的内存也有一个控制块,寻…...

文件读取结束的判定
大家好啊,我们今天来补充文件操作的读取结束的判定。 被错误使用的feof 牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾…...
《基于 Vue 组件库 的 Webpack5 配置》9.module.exports 可为数组类型且注意编译顺序
module.exports常见是对象类型,其实也可用数组类型;注意编译顺序,从后往前 编: 也就是说先编 another.js,再编 index.js;所以代码第 9 行不能设置为 true,仅在第一次,也就是代码第19…...
CUDA学习笔记(四)device管理
本篇博文转载于https://www.cnblogs.com/1024incn/tag/CUDA/,仅用于学习。 device管理 NVIDIA提供了集中凡是来查询和管理GPU device,掌握GPU信息查询很重要,因为这可以帮助你设置kernel的执行配置。 本博文将主要介绍下面两方面内容&…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...
电脑桌面太单调,用Python写一个桌面小宠物应用。
下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡,可以响应鼠标点击,并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...