【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 并非可以修饰任意函数的返回值,换句话说,一个函数要想成为常量表达式,必须满足如下三个条件:

  1. 函数必须有返回值,即函数的返回值类型不能是 void

    constexpr void func() { }  // 函数的返回值类型不能是 void

  2. 整个函数的函数体中,除了可以包含 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;
    }

  3. 函数在使用之前,必须有对应的定义语义。普通函数的调用只需要提前写好该函数的声明部分即可,函数的定义部分可以放在调用位置之后甚至其他文件中,但常量表达式函数在使用前,必须要有该函数的定义

    #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 是一个常量)
}
  1. 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 的值

  2. 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) 获取类型时,编译器将根据以下三条规则得出结果:

  1. 如果表达式为普通变量、普通表达式或者类成员访问表达式,那么 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;
    }
  2. 如果表达式是函数调用,那么 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 类型

  3. 如果表达式是一个左值、或者被括号 () 包围,那么 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/165491.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝牙资讯|AirPods Pro 2推送新固件,苹果Find My功能受到好评

苹果公司今天面向采用 Lightning 端口和 USB-C 端口的 AirPods Pro 2 耳机&#xff0c;更新推出了内部编号为 6A305 的全新固件&#xff0c;高于 10 月 10 日发布的 6A303 更新。 苹果官方并没有公布固件的更新日志&#xff0c;目前尚不清楚具体引入了哪些新功能、新特性。苹…

2019年亚太杯APMCM数学建模大赛A题基于图像分析的二氧化硅熔化表示模型求解全过程文档及程序

2019年亚太杯APMCM数学建模大赛 A题 基于图像分析的二氧化硅熔化表示模型 原题再现 铁尾矿的主要成分是二氧化硅&#xff0c;而二氧化硅是铁尾矿成分中最难熔化的部分。因此&#xff0c;铁尾矿的熔融行为可以用二氧化硅的熔融行为来表示。然而&#xff0c;高温熔池的温度超过…

jdk21的外部函数和内存API(官方翻译)

1、jdk21&#xff1a; 引入一个 API&#xff0c;通过该 API&#xff0c;Java 程序可以与 Java 运行时之外的代码和数据进行互操作。通过有效地调用外部函数&#xff08;即JVM外部的代码&#xff09;和安全地访问外部内存&#xff08;即不由JVM管理的内存&#xff09;&#xf…

统计学习方法 感知机

文章目录 统计学习方法 感知机模型定义学习策略学习算法原始算法对偶算法 学习算法的收敛性 统计学习方法 感知机 读李航的《统计学习方法》时&#xff0c;关于感知机的笔记。 感知机&#xff08;perceptron&#xff09;是一种二元分类的线性分类模型&#xff0c;属于判别模型…

大模型,重构自动驾驶

文&#xff5c;刘俊宏 编&#xff5c;王一粟 大模型如何重构自动驾驶&#xff1f;答案已经逐渐露出水面。 “在大数据、大模型为特征&#xff0c;以数据驱动为开发模式的自动驾驶3.0时代&#xff0c;自动驾驶大模型将在车端、云端上实现一个统一的端到端的平台管理。”毫末智…

uni-app:实现时钟自走(动态时钟效果)

效果 核心代码 使用钩子函数 mounted()&#xff0c;设置定时器&#xff0c;是指每秒都要去执行时间的获取&#xff0c;以至于实现时间自走的效果 mounted() { this.updateTime(); // 初始化时间 setInterval(this.updateTime, 1000); // 每秒更新时间 }, 自定义方法…

NodeMCU ESP8266 读取按键外部输入信号详解(图文并茂)

NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09; 文章目录 NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09;前言按键输入常用接口pinModedigitalRead 示例代码结论 前言 ESP8266如何检测外部信号的输入&#xff0c;通常…

Java封装:面向对象的三大特性之一

&#x1f451;专栏内容&#xff1a;Java⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、封装的概念二、访问修饰限定符三、包1、包的概念2、导入包中的类3、常见的包 嗨&#xff01;前面我们简单的认识了一下什么…

常用的设计模式以及操作Redis、MySQL数据库、各种MQ、数据类型转换的方法

文章目录 &#x1f31f; 如何优雅地写出高质量的Java代码&#x1f34a; 设计模式&#x1f389; 单例模式&#x1f389; 工厂模式&#x1f389; 观察者模式 &#x1f34a; 操作Redis&#x1f389; 连接Redis&#x1f389; 存储数据&#x1f389; 获取数据&#x1f389; 删除数据…

Java注解处理器APT

注解处理器介绍 什么是APT&#xff1f; 在JDK6的时候引入了JSR269的标准&#xff0c;即APT&#xff08;Annotation Processing Tool&#xff09;&#xff0c;用于在编译时处理源代码中的注解&#xff0c;从而生成额外的代码、配置文件或其他资源。与传统的运行时反射相比&…

深入探索Sharding JDBC:分库分表的利器

随着互联网应用的不断发展和用户量的不断增加&#xff0c;传统的数据库在应对高并发和大数据量的场景下面临着巨大的挑战。为了解决这一问题&#xff0c;分库分表成为了一个非常流行的方案。分库分表主流的技术包括MyCat和Sharding JDBC。我们来通过一张图来了解这两者有什么区…

C语言——二周目——程序的翻译与执行环境

一、程序环境 对于一个C语言程序的实现&#xff0c;整个过程一般存在两个不同的环境&#xff0c;分别是翻译环境与执行环境。在翻译环境中&#xff0c;我们所写的源代码经过一系列处理被转换成为可执行的机器指令&#xff1b;在执行环境中&#xff0c;会实际执行代码。 整个程序…

Open3D(C++) 最小二乘拟合平面(拉格朗日乘子法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 设拟合出的平面方程为: a x + b y + c

手写redux的connect方法, 使用了subscribe获取最新数据

一. 公共方法文件 1. connect文件 import React, { useState } from "react"; import MyContext from "./MyContext"; import _ from "lodash";// 模拟react-redux的 connect高阶函数 const connect (mapStateToProps, mapDispatchToProps) &…

数学建模——最优连接(基于最小支撑树)

一、概念 1、图的生成树 由图G(V,E)的生成子图G1(V,E1)(E1是E的子集&#xff09;是一棵树&#xff0c;则称该树为图G的生成树&#xff08;支撑树&#xff09;&#xff0c;简称G的树。图G有支撑树的充分必要条件为图G连通。 2、最小生成树问题 连通图G(V,E)&#xff0c;每条边…

STM32串口

前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 目前已经学习了GPIO的输入输出&#xff0c;但是没有完整的显示信息&#xff0c;最便宜的显示就是串口。 000 -111 AVR单片机 已经学会过了&#xff0c; 提示&#xff1a;以下是本篇文章正文内容&#x…

Servlet的生命周期

2023.10.18 WEB容器创建的Servlet对象&#xff0c;这些Servlet对象都会被放到一个集合当中&#xff08;HashMap&#xff09;&#xff0c;这个集合当中存储了Servlet对象和请求路径之间的关系 。只有放到这个HashMap集合中的Servlet才能够被WEB容器管理&#xff0c;自己new的Ser…

《视觉 SLAM 十四讲》V2 第 10 讲 后端优化2 简化BA 【位姿图】

文章目录 第10讲 后端210.1 滑动窗口滤波 和 优化10.1.2 滑动窗口法 10.2 位姿图10.3 实践&#xff1a; 位姿图优化本讲 CMakeLists.txt 10.3.1 g2o 原生位姿图 【Code】10.3.2 李代数上的位姿优化 【Code】 习题10题1 【没推完】 LaTex 第10讲 后端2 滑动窗口优化 位姿图优化…

最新!两步 永久禁止谷歌浏览器 Google Chrome 自动更新

先放效果图&#xff1a; CSDN这个问题最火的大哥的用了没用 像他这样连浏览器都打不开 为什么要禁止chrome自动更新 看到很多搞笑的大哥&#xff0c;说为啥要禁止&#xff1b; 我觉得最大的原因就是chromedriver跟不上chrome的自动更新&#xff0c;导致我们做selenium爬虫的…

CentOS 7 安装 MySQL 8

一、卸载MariaDB MariaDB是MySQL 的一个分支&#xff0c;完全兼容MySQL&#xff0c;包括API和命令行&#xff0c;使之能轻松成为MySQL的代替品。 1、查看版本&#xff08;如果有就卸载&#xff09; rpm -qa|grep mariadbrpm -e --nodeps 文件名 二、下载mysql 1、进入 /usr/…