深入解析C++的auto自动类型推导(二)

        

目录

使用auto的好处

新标准新增功能

使用auto的限制


        上一篇详细讲解了使用auto关键字进行自动类型推导时的推导规则,这一篇重点讲解auto的使用以及C++14、C++17、C++20等新标准对auto的功能完善,最后再介绍auto的使用限制。上一篇请从这里阅读:深入解析C++的auto自动类型推导(一)

使用auto的好处

  • 强制初始化的作用

        当你定义一个变量时,可以这样写:

int i;

        这样写编译是能够通过的,但是却有安全隐患,比如在局部代码中定义了这个变量,然后又接着使用它了,可能面临未初始化的风险。但如果你这样写:

auto i;

        这样是编译不通过的,因为变量i缺少初始值,你必须给i指定初始值,如下:

auto i = 0;

        必须给变量i初始值才能编译通过,这就避免了使用未初始化变量的风险。

  • 定义小范围内的局部变量时

        在小范围的局部代码中定义一个临时变量,对理解整体代码不会造成困扰的,比如:

for (auto i = 1; i < size(); ++i) {}

        或者是基于范围的for循环的代码,只是想要遍历容器中的元素,对于元素的类型不关心,如:

std::vector<int> v = {};
for (const auto& i : v) {}
  • 减少冗余代码

        当变量的类型非常长时,明确写出它的类型会使代码变得又臃肿又难懂,而实际上我们并不关心它的具体类型,如:

std::map<std::string, int> m;
for (std::map<std::string, int>::iterator it = m.begin(); it != m.end(); ++it) {}

        上面的代码非常长,造成阅读代码的不便,对增加理解代码的逻辑也没有什么好处,实际上我们并不关心it的实际类型,这时使用auto就使代码变得简洁:

​for (auto it = m.begin(); it != m.end(); ++it) {}

        再比如下面的例子:

std::unordered_multimap<int, int> m;
std::pair<std::unordered_multimap<int, int>::iterator,std::unordered_multimap<int ,int>::iterator>range = m.equal_range(k);

        对于上面的代码简直难懂,第一遍看还看不出来想代表的意思是什么,如果改为auto来写,则一目了然,一看就知道是在定义一个变量:

auto range = m.equal_range(k);
  • 无法写出的类型

        如果说上面的代码虽然难懂和难写,毕竟还可以写出来,但有时在某些情况下却无法写出来,比如用一个变量来存储lambda表达式时,我们无法写出lambda表达式的类型是什么,这时可以使用auto来自动推导:

auto compare = [](int p1, int p2) { return p1 < p2; }
  • 避免对类型硬编码

        除了上面提到的可以减少代码的冗余之外,使用auto也可以避免对类型的硬编码,也就是说不写死变量的类型,让编译器自动推导,如果我们要修改代码,就不用去修改相应的类型,比如我们将一种容器的类型改为另一种容器,迭代器的类型不需要修改,如:

std::map<std::string, int> m = { ... };
auto it = m.begin();
// 修改为无序容器时
std::unordered_map<std::string, int> m = { ... };
auto it = m.begin();

        C++标准库里的容器大部分的接口都是相同的,泛型算法也能应用于大部分的容器,所以对于容器的具体类型并不是很重要,当根据业务的需要更换不同的容器时,使用auto可以很方便的修改代码。

  • 跨平台可移植性

        假如你的代码中定义了一个vector,然后想要获取vector的元素的大小,这时你调用了成员函数size来获取,此时应该定义一个什么类型的变量来承接它的返回值?vector的成员函数size的原型如下:

size_type size() const noexcept;

        size_type是vector内定义的类型,标准库对它的解释是“an unsigned integral type that can represent any non-negative value of difference_type”,于是你认为用unsigned类型就可以了,于是写下如下代码:

​std::vector<int> v;
unsigned sz = v.size();

        这样写可能会导致安全隐患,比如在32位的系统上,unsigned的大小是4个字节,size_type的大小也是4个字节,但是在64位的系统上,unsigned的大小是4个字节,而size_type的大小却是8个字节。这意味着原本在32位系统上运行良好的代码可能在64位的系统上运行异常,如果这里用auto来定义变量,则可以避免这种问题。

  • 避免写错类型

        还有一种似是而非的问题,就是你的代码看起来没有问题,编译也没有问题,运行也正常,但是效率可能不如预期的高,比如有以下的代码:

std::unordered_map<std::string, int> m = { ... };
for (const std::pair<std::string, int> &p : m) {}

        这段代码看起来完全没有问题,编译也没有任何警告,但是却暗藏隐患。原因是std::unordered_map容器的键值的类型是const的,所以std::pair的类型不是std::pair<std::string, int>而是std::pair<const std::string, int>。但是上面的代码中定义p的类型是前者,这会导致编译器想尽办法来将m中的元素(类型为std::pair<const std::string, int>)转换成std::pair<std::string, int>类型,因此编译器会拷贝m中的所有元素到临时对象,然后再让p引用到这些临时对象,每迭代一次,临时对象就被析构一次,这就导致了无故拷贝了那么多次对象和析构临时对象,效率上当然会大打折扣。如果你用auto来替代上面的定义,则完全可以避免这样的问题发生,如:

for (const auto& p : m) {}

新标准新增功能

  • 自动推导函数的返回值类型(C++14)

        C++14标准支持了使用auto来推导函数的返回值类型,这样就不必明确写出函数返回值的类型,如下的代码:

template<typename T1, typename T2>
auto add(T1 a, T2 b) {return a + b;
}int main() {auto i = add(1, 2);
}

        不用管传入给add函数的参数的类型是什么,编译器会自动推导出返回值的类型。

  • 使用auto声明lambda的形参(C++14)

        C++14标准还支持了可以使用auto来声明lambda表达式的形参,但普通函数的形参使用auto来声明需要C++20标准才支持,下面会提到。如下面的例子:

​auto sum = [](auto p1, auto p2) { return p1 + p2; };

        这样定义的lambda式有点像是模板,调用sum时会根据传入的参数推导出类型,你可以传入int类型参数也可以传入double类型参数,甚至也可以传入自定义类型,如果自定义类型支持加法运算的话。

  • 非类型模板形参的占位符(C++17)

        C++17标准再次拓展了auto的功能,使得能够作为非类型模板形参的占位符,如下的例子:

template<auto N>
void func() {std::cout << N << std::endl;
}func<1>();		// N为int类型
func<'c'>();	// N为chat类型

        但是要保证推导出来的类型是能够作为模板形参的,比如推导出来是double类型,但模板参数不能接受是double类型时,则会导致编译不通过。

  • 结构化绑定功能(C++17)

        C++17标准中auto还支持了结构化绑定的功能,这个功能有点类似tuple类型的tie函数,它可以分解结构化类型的数据,把多个变量绑定到结构化对象内部的对象上,在没有支持这个功能之前,要分解tuple里的数据需要这样写:

tuple x{1, "hello"s, 5.0};
itn a;
std::string b;
double c;
std::tie(a, b, c) = x;	// a=1, b="hello", c=5.0

        在C++17之后可以使用auto来这样写:

tuple x{1, "hello"s, 5.0};
auto [a, b, c] = x;	// 作用如上
std::cout << "a=" << a << ", b=" << b << ", c=" << c << std::endl;

        auto的推导功能从以前对单个变量进行类型推导扩展到可以对一组变量的推导,这样可以让我们省略了需要先声明变量再处理结构化对象的麻烦,特别是在for循环中遍历容器时,如下:

std::map<std::string, int> m;
for (auto& [k, v] : m) {std::cout << k << " => " << v << std::endl;
}
  • 使用auto声明函数的形参(C++20)

        之前提到无法在普通函数中使用auto来声明形参,这个功能在C++20中也得到了支持。你终于可以写下这样的代码了:

auto add (auto p1, auto p2) { return p1 + p2; };
auto i = add(1, 2);
auto d = add(5.0, 6.0);
auto s = add("hello"s, "world"s);	// 必须要写上s,表示是string类型,默认是const char*,// char*类型是不支持加法的

        这个看起来是不是和模板很像?但是写法要比模板要简单,通过查看生成的汇编代码,看到编译器的处理方式跟模板的处理方式是一样的,也就是说上面的三个函数调用分别产生出了三个函数实例:

auto add<int, int>(int, int);
auto add<double, double>(double, double);
auto add<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >);

使用auto的限制

        上面详细列出了使用auto的好处和使用场景,但在有些地方使用auto还存在限制,下面也一并罗列出来。

  • 类内初始化成员时不能使用auto

        在C++11标准中已经支持了在类内初始化数据成员,也就是说在定义类时,可以直接在类内声明数据成员的地方直接写上它们的初始值,但是在这个情况下不能使用auto来声明非静态数据成员,比如:

class Object {auto a = 1;	// 编译错误。
};

        上面的代码会出现编译错误:error: 'auto' not allowed in non-static class member。虽然不能支持声明非静态数据成员,但却可以支持声明静态数据成员,在C++17标准之前,使用auto声明静态数据成员需要加上const修饰词,这就给使用上造成了不便,因此在C++17标准中取消了这个限制:

class Object {static inline auto a = 1;	// 需要写上inline修饰词
};
  • 函数无法返回initializer_list类型

        虽然在C++14中支持了自动推导函数的返回值类型,但却不支持返回的类型是initializer_list<T>类型,因此下面的代码将编译不通过:

auto createList() {return {1, 2, 3};
}

        编译错误信息:error: cannot deduce return type from initializer list。

  • lambda式参数无法使用initializer_list类型

        同样地,在lambda式使用auto来声明形参时,也不能给它传递initializer_list<T>类型的参数,如下代码:

std::vector<int> v;
auto resetV = [&v](const auto& newV) { v = newV; };
resetV({1, 2, 3});

        上面的代码会编译错误,无法使用参数{1, 2, 3}来推导出newV的类型。


本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。

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

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

相关文章

线程池核心原理浅析

前言 由于系统资源是有限的&#xff0c;为了降低资源消耗&#xff0c;提高系统的性能和稳定性&#xff0c;引入了线程池对线程进行统一的管理和监控&#xff0c;本文将详细讲解线程池的使用、原理。 为什么使用线程池 池化思想 线程池主要用到了池化思想&#xff0c;池化思想…

【计算机科学速成课】笔记一

文章目录 写在前面1.计算机的早期历史2.电子计算机3.布尔运算和逻辑门4.二进制5.算术逻辑单元-ALU6.寄存器和内存 写在前面 所有的一切源于这样一个网站——CS自学指南。 这是新手小白入门计算机科学必要了解的知识——【计算机科学速成课】[40集全/精校] - Crash Course Comp…

Dragonfly 拓扑的路由算法

Dragonfly 拓扑的路由算法 1. Dragonfly 上的路由 (1)最小路由(2)非最小路由 2. 评估3. 存在问题 (1)吞吐量限制(2)较高的中间延迟 references Dragonfly 拓扑的路由算法 John Kim, William J. Dally 等人在 2008 年的 ISCA 中提出技术驱动、高度可扩展的 Dragonfly 拓扑。而…

在做题中学习(53): 寻找旋转数组中的最小值

153. 寻找旋转排序数组中的最小值 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;O(logn)->很可能就是二分查找 思路&#xff1a;再看看题目要求&#xff0c;可以画出旋转之后数组中元素的大小关系&#xff1a; 首先&#xff0c;数组是具有二段性的(适配二分查…

数据库(MySQL)—— 索引

数据库&#xff08;MySQL&#xff09;—— 索引 什么是索引创建索引使用 CREATE INDEX 语句使用 ALTER TABLE 语句在创建表时定义索引特殊类型索引注意事项 举个例子无索引的情况有索引的情况为什么索引快索引的结构 今天我们来看看MySQL中的索引&#xff1a; 什么是索引 MyS…

财政部、交通运输部:推动北斗导航等新技术与交通基础设施融合

财政部、交通运输部&#xff1a;推动北斗导航等新技术与交通基础设施深度融合 近日&#xff0c;为深入贯彻落实中共中央、国务院关于加快建设交通强国、数字中国等决策部署&#xff0c;推进公路水路交通基础设施数字转型、智能升级、融合创新&#xff0c;加快发展新质生产力&a…

VisualGDB:Linux动态库项目创建、编译及库的使用

此篇接上篇 《VisualGDB:为Linux项目添加系统依赖库》,在本篇中我们重点分享一下如何基于VisualGDB 在VS中创建Linux动态库项目,如何编译及使用创建的动态库。 一、VisualGDB创建Linux动态库项目 如下,我们创建一个Linux下的动态库项目MyMath 二、编译动态库 我们稍微…

哈夫曼编码python算法实现(图片版)

一、问题&#xff1a; 请使用哈夫曼编码方法对给定的字符串&#xff0c;进行编码&#xff0c;以满足发送的编码总长度最小&#xff0c;且方便译码。“AABBCCDDEEABCDDCDBAEEAAA” 二、过程&#xff1a; 三、结果&#xff1a;

手动实现简易版RPC(四)

手动实现简易版RPC(四) 往期内容 手动实现简易版RPC&#xff08;一&#xff09;&#xff1a;RPC简介及系统架构 手动实现简易版RPC&#xff08;二&#xff09;&#xff1a;简单RPC框架实现 手动实现简易版RPC(三)&#xff1a;mock数据生成 前言 接上几篇博客我们实现了最…

【6D位姿估计】FoundationPose 跑通demo 训练记录

前言 本文记录在FoundationPose中&#xff0c;跑通基于CAD模型为输入的demo&#xff0c;输出位姿信息&#xff0c;可视化结果。 然后分享NeRF物体重建部分的训练&#xff0c;以及RGBD图为输入的demo。 1、搭建环境 方案1&#xff1a;基于docker镜像&#xff08;推荐&#xf…

重置密码之后无法ssh登录

背景描述 我这边有个服务器S&#xff0c;我从ServerA可以ssh上去&#xff0c;但是我从堡垒机B无法ssh上去&#xff1b;一开始以为是密码问题&#xff0c;手动重置密码&#xff0c;但是依然无法登录进去&#xff1b;一直提示密码错误&#xff1b;改了好几次密码都不行 问题原因…

5.9号模拟前端面试10问

5.9号模拟前端面试10问 1.html语义化的理解 HTML语义化是指使用具有明确含义的HTML标签来描述内容&#xff0c;而不仅仅是使用<div>和<span>等通用容器标签。语义化的HTML代码更易于阅读和维护&#xff0c;同时也有助于搜索引擎优化&#xff08;SEO&#xff09;。…

达梦数据库限制用户登录IP测试

达梦数据库创建用户时可以限制登录ip和时间段。 创建测试测试用户 create user test1 identified by Test_1234 ALLOW_IP "192.168.100.101"; 限定该用户只能通过192.168.100.101地址登录数据库 连接测试 上图可见&#xff0c;192.168.100.101客户端可以连接上19…

wish、亚马逊怎么给店铺引流?怎么运用自养号测评提高流量的转化率?

作为全球知名的跨境电商平台&#xff0c;wish、亚马逊为卖家提供了一个拓展海外市场的机会。然而&#xff0c;在wish、亚马逊平台上建立和经营一家成功的店铺需要有效的引流策略。那么&#xff0c;Wish、亚马逊怎样才能给店铺引流呢&#xff1f; 一、Wish、亚马逊怎么给店铺引…

C++学习笔记——仿函数

文章目录 仿函数——思维导图仿函数是什么仿函数的优势理解仿函数仿函数的原理举例 仿函数——思维导图 仿函数是什么 使用对象名调用operator&#xff08;&#xff09;函数看起来像是在使用函数一样&#xff0c;因此便有了仿函数的称呼&#xff1b;仿函数存在的意义是&#x…

javaMail快速部署——发邮件喽~

目录 功能阐述 前序步骤 &#xff08;1&#xff09;到QQ邮箱中获取到授权码 代码实现 坑 今天在写一个修改密码的功能的时候要用到邮箱的发送&#xff0c;然后因为这个项目比较老旧了&#xff0c;采用的是javaWeb和jsp的配置&#xff0c;对于我只使用过springBoot整合的ja…

苹果新款 M4 芯片专注于 AI

爆炸性消息&#xff01;苹果的新一代 M4 芯片来了&#xff01;这家伙拥有 38 万亿次操作的超强神经引擎&#xff0c;速度比苹果 A11 芯片的 NPU 快 60 倍&#xff01;虽然它的速度还没有达到 Snapdragon X Elite 的 45 TOPS&#xff0c;但苹果自夸 M4 将提供与最新 PC 芯片相同…

带你入门React

目录 前言一&#xff0c;基本配置1.1 环境搭建1.2 页面初始化渲染二&#xff0c;基础学习2.1 结构与样式开发2.2 数据展示2.3 行内样式2.4 条件渲染2.5 列表渲染2.6 点击事件 三&#xff0c;页面更新3.1 组件数据3.2 组件数据共享 总结 前言 笔者之前的工作经验都局限于Vue&am…

ICode国际青少年编程竞赛- Python-2级训练场-for循环中的变量

ICode国际青少年编程竞赛- Python-2级训练场-for循环中的变量 1、 for i in range(4):Dev.turnLeft()# 将i1作为Dev移动的步数Dev.step(i 1)2、 for i in range(4):Spaceship.step(i 1)Dev.step(3)Dev.step(-3)3、 for i in range(5):Dev.step(5 - i)Dev.turnRight()4、 …

开源文档管理系统Paperless-ngx如何在Linux系统运行并发布至公网

文章目录 1. 部署Paperless-ngx2. 本地访问Paperless-ngx3. Linux安装Cpolar4. 配置公网地址5. 远程访问6. 固定Cpolar公网地址7. 固定地址访问 Paperless-ngx是一个开源的文档管理系统&#xff0c;可以将物理文档转换成可搜索的在线档案&#xff0c;从而减少纸张的使用。它内置…