C++11:系统类型增强

C++11:系统类型增强

    • 强枚举类型
      • 作用域限定
      • 隐式类型转换
      • 指定类型
      • 前置声明
    • 类型别名 using
      • 模板别名
      • 复杂指针别名
    • auto
      • 限制性 auto
      • 注意事项
    • nullptr
    • decltype


强枚举类型

在C++98的枚举设计中,存在很多缺陷,为此C++11推出了强枚举来代替旧版的枚举,提供更加安全可靠的枚举类型。

强枚举的语法如下:

enum class e : type
{val1 = 1,val2 = 2
}

此处定义了一个名为e的强枚举,只需要在enum后面加一个calss关键字即可,此处的: type用于指定底层类型,后面讲解,可以省略。


作用域限定

旧版本的 enum 会直接把枚举内部的值放到外层作用域,如果多个枚举有一样的值,或者在相同作用域定义了与枚举同名的变量,那么就会导致命名冲突:

enum e1
{a = 1
};enum e2
{a = 2
};

以上代码中,e1::ae2::a发生了冲突,全局作用域存在两个叫做a的变量。

在强枚举中,每个枚举自成一个作用域,不会污染外部变量,相互之间也不会冲突

enum class e1
{a = 1
};enum class e2
{a = 2
};

以上代码就不会报错了,因为e1e2都是强类型枚举,各自作用域独立,枚举也不会放到全局作用域。

由于枚举值不在全局作用域了,那么也就不能直接访问a了,必须通过域限定符来访问:e1::ae2::a


隐式类型转换

旧版本的 enum 可以随意的隐式转换成其他类型,这是从C语言继承下来的极其不安全的特性,它可以转化为任意整形家族,甚至charboolfloat

enum type_old
{a = 128
};int main()
{int x = a;signed char c = a;long long ll = a;float f = a;return 0;
}

以上代码是合法的(在 vs2022g++13.3编译均通过),这非常不安全,例如此处的 a = 128,它其实是超出了 signed char 的范围的,但是他不仅没有报错,而且还隐式的发生了截断,此时如果再std::cout << (int)c,你会得到-128这个值。

强枚举有非常严格的类型限定,他不能隐式转化为强枚举之外的类型。

enum class type_new
{a = 1
};int main()
{// err: 不允许隐式转化int x = type_new::a;type_new e1 = 1;// success: 显示类型转换int y = static_cast<int>(type_new::a);type_new e2 = (type_new)1;// success: C风格显示类型转换int z = (int)type_new::a;type_new e3 = static_cast<type_new>(1);return 0;
}

使用强枚举后,既不允许从其他类型隐式转为强枚举(哪怕这个值在枚举中存在),也不允许强枚举隐式转为其他类型。如果需要转换,那么必须用static_cast或者C语言风格的显式类型转换


指定类型

在旧版enum中,其类型往往是不确定的,这可能随着编译器不同而变化,一般为int。就算你传入一个long long类型,最后也会被转回int

这是因为 在C++标准中规定:编译器指定的枚举底层类型,只要可以存储所有的枚举值即可

例如你的枚举值是1 2 3,那么就有可能用short这样的来存储,如果再大一点就可能是int。不过就算C++标准这么规定,其实大部分主流编译器都固定使用int,不论你数据范围是多少,例如MSVCgcc

enum type_old
{a = INT_MAX + 1ll // 此处 1ll 表示 long long 字面量,后缀ll不可省略
};int main()
{std::cout << a << std::endl;return 0;
}

以上代码中,type_old::a这个枚举接受了一个大于int最大值的long long字面量,程序正常运行,最后输出结果为:-2147483648也就是int的最小值,这是因为发生了从long long -> int的隐式截断,你无法在 C++98 的枚举中存储大于int范围的值。相应的,当你存储的数据范围比较小,比如枚举值都是0 ~ 127,你也不能用一个字节来存,必须用int,(或者你可以祈祷某种编译器检测到了你的数据范围比较小,给你改用小范围的类型来存储)。

在强枚举中,可以指定枚举值的底层类型:

enum class type_new : long long
{a = INT_MAX + 1ll
};int main()
{std::cout << static_cast<long long>(type_new::a) << std::endl;return 0;
}

代码中: long long指定了枚举的底层使用long long存储,最后程序输出:2147483648,也就是INT_MAX + 1

要注意的是,如果你在C++11环境运行以下代码,也是合法的:

enum type_new : long long // 此处把 class 删掉了,是普通枚举
{a = INT_MAX + 1ll
};

因为C++11在更新强枚举的同时,对旧版枚举也做了优化,允许普通枚举也指定底层类型!但是普通枚举作用域,隐式转化等特性依然保留。

前置声明

旧版枚举是不允许前置声明的,例如以下代码会报错:

enum type_old;void func(type_old e)
{
}enum type_old
{a = 128
};int main()
{func(a);return 0;
}

以上代码在C++98环境运行会报错,刚才说过,枚举底层使用什么类型是不确定的,在不同编译器可能不同,这就导致func函数的第一个参数type_old声明后,无法确定其内存大小,从而编译失败

在C++11中,其实强枚举直接这么写也会报错,例如把上面的声明改成:enum class type_new这样的强枚举,还是会报错。

根本原因是无法确定枚举的底层变量,从而无法得知大小。因为C++标准明确说了编译器可以自己来指定底层变量,在枚举值还没有定义之前,编译器根本就无法推断用什么类型来存,也就不知道这个枚举的底层类型。

此时上一个特性就派上用场了,用户可以自己显式指定枚举底层类型,那编译器不就明确了枚举的大小了么

而刚才又说过,C++11对普通枚举和强枚举都支持指定底层类型,那么代码就可以这样改写:

enum type_old : int;
enum type_new : int;void func(type_old e1, type_new e2)
{
}enum type_old : int
{a = 128
};enum type_new : int
{a = 128
};

现在不论强枚举还是普通枚举,都可以提前声明了,因为通过: int明确指定了底层使用int存储,那么func的两个参数就知道自己要给参数预留多少空间,此时前置声明就有用了。


类型别名 using

在 C++11 之前,typedef 是定义类型别名的唯一方式,但它存在语法局限,尤其是在模板编程中不够灵活。

在C++11之前,using主要用于展开命名空间,或者声明其它命名空间内部的变量。

C++11 给 using 添加了类型别名的功能,不仅替代了 typedef,还提供了更强大的功能,特别是在模板别名和复杂类型表达式简化方面。

语法如下:

using new_name = old_name;

模板别名

如果想给一系列模板类取别名,例如希望简化list的迭代器类型std::list<T>::iterator变成list_it<T>,使用typedef是无法做到的,而using就可以配合模板使用:

template<typename T>
using list_it = std::list<T>::iterator;int main()
{list_it<int> p; // successreturn 0;
}

这是typedef无法做到的,也是using最大的优势。

复杂指针别名

在使用typedef给函数指针或者数组指针取别名的时候,语法会很复杂,而且可读性很差,例如:

// 把 void(*)(int, int) 类型的函数指针取别名为 func_ptr
typedef void(*func_ptr)(int, int);// 把 int(*)[] 类型的数组指针取别名为 arr_ptr
typedef int(*arr_ptr)[];

这是因为在typedef中,要求新名称必须写在*后面,这样编译器才知道这个新的名称是一个指针,这就导致可读性很差,例如一个带有回调函数的函数指针取别名:

typedef void(*func_ptr)(void(*)(void), int);

此处func_ptr的类型是void(*)(void(*)(void), int),如果C语言基础差一些,这段代码要琢磨一点时间。

using中,无需把新名称写到*后面,就是固定写在=左边,右边就是原始类型,例如:

// 把 void(*)(int, int) 类型的函数指针取别名为 func_ptr
using func_ptr = void(*)(int, int);// 把 int(*)[] 类型的数组指针取别名为 arr_ptr
typedef arr_ptr = int(*)[];

这样语义就明确很多了,程序员一下就看出来新名称是什么,原始类型是什么。


auto

在C++中,auto关键字可以用来自动推断变量的类型,它在编译时会根据初始化表达式的类型来确定变量的类型。

使用auto的主要好处是可以简化代码并提高可读性。它可以减少手动指定变量类型的工作,并且可以防止类型错误。相比于显式指定变量类型,使用auto可以让代码更加灵活和易于维护。

  1. 自动推断基本类型变量的类型
auto age = 25; // 推断age为int类型
auto salary = 5000.50; // 推断salary为double类型

auto也可以自动推断指针的类型,比如这样:

int x = 10;
auto y = &x;

此时y的类型自动判别为int*

实际上不建议这么做,C++中最好还是明确每个变量的类型,对于这种简单的类型还是不要用auto的好。

  1. 自动推断非常长的类型
std::vector<int> numbers = {1, 2, 3, 4, 5};for (auto it = numbers.begin(); it != numbers.end(); ++it) 
{std::cout << *it << " ";
}

有的时候获得变量的类型会需要很长的代码,使用auto可以缩短变量类型的长度,这是C++推荐的做法,当然用户心里还是要清楚auto最后接收到了什么类型,只是懒得写出来而已。

  1. 接受不确定的类型
auto add = [](int a, int b){ return a + b; };

lambda表达式中,返回值类型是不确定的,必须用auto接收。这是因为lambda设计出来,就是只用一次就不再用的匿名函数,那么用户就无需知道这个表达式的类型,因为拿到类型就可以再去定义相同的函数了,那就不要用lambda,直接写一个函数/仿函数就行了。因此C++中lambda的类型是随机生成的,必须用auto才能接收。


限制性 auto

除去基本的类型推断,auto可以限制接收到的类型必须是指针或引用。

看到一段代码:

int x = 10;auto* a1 = x;
auto* a2 = &x;
auto a3 = &x;

auto* a1 = x;中,x的类型是int,那么auto本应将其值判别为int,但是由于auto**限制了,此时auto必须得到一个指针,所以编译器会报错;而auto* a2 = &x;得到的就是指针,此时代码不会报错,可以正常识别为int*

在本质上auto* a2 = &x;auto a3 = &x;的结果是没有区别的,只是auto*要求得到的必须是一个指针类型,而auto不限制其类型。

同理auto&也可以限定类型必须是一个引用,否则会报错。


注意事项

  1. auto不能作为函数的参数
  2. auto不能用于声明数组

比如以下代码:

int arr1[] = {1, 3, 5, 7, 9};
auto arr2[] = {1, 3, 5, 7, 9};

此时第二条代码就会报错,因为其用auto类型定义了一个数组。

  1. 在同一行定义多个变量时,如果将auto作为其类型,必须一整行都是同一个类型的变量。

比如以下代码:

int x = 1, y = 2;
auto a = 3, b = 4;
auto c = 5, d = 6.0;

以上代码中,auto a = 3, b = 4;是合法的,因为一行内都是int类型。

但是auto c = 5, d = 6.0;是非法的,因为同一行内有不同类型,会报错。


nullptr

在C++11后,推出了新的空指针nullptr,明明已经有NULL了,为啥还需要nullptr?

NULL在C语言中,表示的是((void*)0),也就是被强制转为void*类型的0。但是在C++中,NULL就是整数0

比如可以用刚才学的typeid验证一下:

cout << typeid(NULL).name() << endl;

输出结果为:int,这下就石锤了NULL在C++中就是int

这会导致不少问题,比如这样:

void func(int x)
{cout << "参数为整型" << endl;
}void func(void* x)
{cout << "参数为指针" << endl;
}int main()
{func(NULL);return 0;
}

以上代码中,func函数有两个重载,一个是参数为指针,一个是参数为整型。我现在就是想传一个空指针去调用指针版本的func。但是最后还是会调用int类型的。

nullptr不一样,nullptr不仅不是整型,而且其也不是void*。C++给了nullptr一个专属类型nullptr_t。这个类型有一个非常非常大的优势,该类型只能转化为其它指针类型,不能转化为指针以外的类型

比如以下代码:

int x1 = NULL;//正确
int x2 = nullptr;//错误

因为NULL本质是0,其可以转化为很多非指针类型,比如intdoublechar。但是nullptrnullptr_t,它只能转化为其他指针。上述代码中,我们把nullptr转化为一个int,此时编译器会直接报错,绝对禁止这个行为。

但是这样是可以的:

void* p1 = nullptr;
int* p2 = nullptr;
char* p3 = nullptr;
double* p4 = nullptr;

可以看到,nullptr保证了指针类型的稳定,空指针不会被传递到指针以外的类型。因此nullptr在各方面都有足够的优势,以更加安全的形式给用户提供空指针。


decltype

在C++11以前,有一个关键字typeid,其可以识别一个类型,并且可以通过name成员函数来输出类型名。

比如这样:

int i = 0;
int* pi = &i;cout << typeid(i).name() << endl;
cout << typeid(pi).name() << endl;

输出结果为:

int
int * __ptr64

也就是说,我们可以通过typeid来检测甚至输出变量类型。

decltype也是用于识别类型的,但是decltypetypeid应用方向不同。

decltype可以检测一个变量的类型,并且拿这个类型去声明新的类型

比如这样:

int i = 0;
decltype(i) x = 5;

decltype(i)检测出i的类型为int,于是decltype(i)整体就变成int,从而定义出一个新的变量x

autodecltype 的区别在于,decltype声明变量可以无需初始化。

int a;
decltype(a) b; // success
auto c = a;    // success
auto d;        // error

decltype还可以作为模板参数

例如把lambda传给std::priority_queue作为比较条件:

auto comp = [](const std::string& a, const std::string& b) {return a.size() >= b.size();};std::priority_queue<std::string, std::deque<std::string>, decltype(comp)> q(comp);

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

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

相关文章

linux 内核warn_on/Bug_on

1,warn_on() warn_on() 是 Linux 内核中用于报告潜在问题或警告的宏。与 bug_on() 不同&#xff0c;bug_on() 通常用于报告严重错误&#xff0c;其触发往往会导致内核Oops或panic&#xff0c;而 warn_on() 则用于报告不太严重的、可能只是潜在问题或预期外情况的情况。它的触…

SQL输出20个9

在SQL Server中要输出20个连续的9&#xff0c;可以使用以下几种方法&#xff1a; 使用REPLICATE函数重复生成字符&#xff1a; SELECT REPLICATE(9, 20) AS Result 2. 使用UNION ALL联合查询生成多行&#xff1a; SELECT 9 AS Number FROM (VALUES (1),(1),(1),(1),(1),(1),…

懒人云电脑方案:飞牛NAS远程唤醒 + 节点小宝一键唤醒、远程控制Windows!

后台高频问题解答&#xff1a; “博主&#xff0c;飞牛NAS能定时开关机了&#xff0c;能不能让它顺便把家里Windows电脑也远程唤醒控制&#xff1f;最好点一下就能连&#xff0c;不用记IP端口那种&#xff01;” 安排&#xff01;今天这套方案完美实现&#xff1a; ✅ 飞牛NAS…

Linux特殊符号

1 管道符| 管道符号 | 用于将一个命令的输出作为另一个命令的输入。这种机制允许将多个命令组合在一起&#xff0c;形成一个数据处理链&#xff0c;每个命令处理前一个命令的输出&#xff0c;从而实现复杂的数据处理任务。示例 # 查询/var/log目录下所有的log文件,并进行分页…

初识Docker:容器化技术的入门指南

初识Docker&#xff1a;容器化技术的入门指南 一、Docker是什么&#xff1a;容器化技术的核心概念二、Docker的核心优势2.1 环境一致性2.2 高效部署与快速迭代2.3 资源利用率高 三、Docker的安装与基本使用3.1 安装Docker3.2 Docker基本概念3.3 第一个Docker容器体验 四、Docke…

商务风企业公司推广培训计划PPT模版分享

商务风企业公司推广培训计划PPT模版分享&#xff1a;商务培训推广计划PPT模版https://pan.quark.cn/s/063282eaf739 第1套PPT模版&#xff0c;绿橙配色&#xff0c;几何图形拼接背景&#xff0c;有中英文标题和占位文本。 第2套PPT模版是黑金高端商务风格&#xff0c;有汇报人…

深入理解Nginx:详尽配置手册

Nginx是一款高性能的HTTP和反向代理服务器&#xff0c;广泛应用于负载均衡、缓存和Web服务器等场景。随着互联网应用的快速发展&#xff0c;掌握Nginx的配置和优化技巧显得尤为重要。在本篇文章中&#xff0c;我们将深入探讨Nginx的配置&#xff0c;帮助你更好地理解和使用这款…

每日leetcode

1572. 矩阵对角线元素的和 - 力扣&#xff08;LeetCode&#xff09; 题目 给你一个正方形矩阵 mat&#xff0c;请你返回矩阵对角线元素的和。 请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。 示例 1&#xff1a; 输入&#xff1a;mat [[1,2,3], …

Server 9 ,在 VMware 虚拟机上安装 Windows 系统完整指南

目录 前言 一、准备工作 1.1 准备安装文件 1.2 安装VMware软件 1.3 创建新的虚拟机 1.4 开启虚拟机 二、注意事项 2.1 调整硬件设置 2.2 启动顺序配置 2.3 固件类型选择 2.4 安全启动配置 三、安装优化 3.1 安装VMware Tools 3.2 系统更新与激活 四、更多操作 ​…

最终章:终焉之塔 · 前端之道

第一章&#xff1a;HTML基石现实的骨架 第二章&#xff1a;CSS秘典 色彩与布局的力量 第三章&#xff1a;JavaScript引擎 行为之火 第四章&#xff1a;DOM迷宫 掌控页面之心 第五章&#xff1a;异步幻境 时间与数据的秘密 第六章&#xff1a;事件风暴 用户的意志 第七章&a…

详解 .net9 内置 Lock 对象,更加现代化和灵活可控的锁对象

.NET 9 引入了全新的 System.Threading.Lock 类型&#xff0c;作为更现代、类型安全且具备递归支持的同步原语。与传统的基于 Monitor.Enter/lock(obj) 的方式不同&#xff0c;Lock 是一个具体的类&#xff0c;提供了更灵活的 API 和结构化编程模型。 Lock 类 Lock 是一个具体…

python几行命令实现快速打包apk

1. ​​环境准备​ sudo apt update sudo apt install -y python3-pip git zip unzip openjdk-17-jdk sudo apt-get install -y autoconf automake libtool pip install kivy buildozer cython2. ​​项目配置​ 在项目目录中初始化Buildozer&#xff1a; buildozer init这会…

实时数仓和离线数仓的区别是什么?企业如何选择合适的数仓架构?

实时数仓和离线数仓的区别是什么&#xff1f;企业如何选择合适的数仓架构&#xff1f; 时数仓和离线数仓都是数据仓库的不同类型&#xff0c;用于存储和管理企业的数据&#xff0c;但它们在数据处理和使用的时间、速度以及用途方面有明显的区别。 在介绍实时数仓之前&#xf…

Docker Desktop for Windows 系统设置说明文档

1. 文档概述 本文档旨在详细说明 Docker Desktop for Windows 应用程序中“设置 (Settings)”界面下的所有可配置选项及其子选项。对于每个配置项&#xff0c;我们将提供其功能描述、推荐配置&#xff08;如适用&#xff09;以及相关注意事项&#xff0c;帮助用户更好地理解和…

精准监测,健康无忧--XC3576H工控主板赋能亚健康检测仪

在快节奏的现代生活中&#xff0c;亚健康问题逐渐成为困扰人们健康的隐形杀手。疲劳、失眠、免疫力下降等问题频发&#xff0c;却往往因难以察觉而延误调理。智能亚健康检测仪通过高科技手段&#xff0c;帮助用户实时了解身体状况&#xff0c;提前预警潜在健康风险。 其核心功能…

SBT开源构建工具

SBT 的多元定义与核心解释 SBT&#xff08;Simple Build Tool&#xff09;是专为 Scala 和 Java 项目设计的开源构建工具&#xff0c;基于 Scala 语言开发&#xff0c;提供依赖管理、编译、测试、打包等全流程支持。其核心特点包括&#xff1a; 核心功能与特性&#xff1a; …

npm run build后将打包文件夹生成zip压缩包

安装依赖 npm install archiver --save-dev准备compress.js文件 const fs require(fs); const archiver require(archiver);const sourceDir ./dist; //替换为你的文件夹路径 const outputZip ./dist.zip;console.log(开始压缩); const output fs.createWriteStream(ou…

力扣 215 .数组中的第K个最大元素

文章目录 题目介绍题解 题目介绍 题解 法一&#xff1a;基于快速排序的选择方法 以中间元素pivot为基准进行排序后&#xff0c;右指针 r 的位置就是最终全部排序好后pivot的位置&#xff0c;然后去左边或右边递归寻找第k个位置&#xff08;答案&#xff09;的元素。 代码如下…

CentOS 7.0重置root密码

文章目录 版本&#xff1a;CentOS 7.0内核版本&#xff1a;CentOS Linux, with Linux 3.10.0-123.el7.x86_64 服务器重启后&#xff0c;等待进入上述页面&#xff0c;按⬆⬇键&#xff0c;中断正常启动。在此页面按E&#xff0c;进入编辑模式 继续按⬇&#xff0c;找到linux16…

Linux之高效文本编辑利器 —— vim

目录 一、vim的基本概念 二、Vim 的三种基本模式 1. 命令模式&#xff08;Command Mode&#xff09; 2. 插入模式&#xff08;Insert Mode&#xff09; 3. 底行模式&#xff08;Last Line Mode&#xff09; 模式切换方法 IDE例子&#xff1a; 三、vim的基本操作 进入vim…