深入解析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.hqwc.cn/news/681544.html

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

相关文章

基于Unity为Vision Pro 构建游戏的4个关键

为Vision Pro开发游戏时需要考虑的四个关键概念:输入的自然性、物理尺寸的真实匹配、交互空间的充足性以及Unity组件的有效利用。 AVP交互小游戏(Capsule Critters)作者分享了使用Unity构建的几个核心关键: Bounded - 游戏定义:Bounded(有限)是Unity的术语,指的是游戏作…

健康体检系统源码,体检中心、医院体检科管理系统,C/S架构,成熟稳定运行。支持预约管理、检查项目管理、结果录入、报告生成、数据分析

一套C/S架构的智慧健康体检管理系统源码&#xff0c;三级综合医院应用案例 体检系统是一种用于管理和记录个人或群体健康信息的软件系统。它通常包括预约管理、检查项目管理、结果录入、报告生成、数据分析等功能&#xff0c;旨在提高体检流程的效率和准确性&#xff0c;为医院…

Eayswoole 报错 crontab info is abnormal

在执行一个指定的定时任务时 如 php easyswoole crontab show 报错 crontab info is abnormal 如下图所示&#xff1a; 查询了半天 修改了如下配置&#xff1a; 旧的 // 创建定时任务实例 $crontab new \EasySwoole\Crontab\Crontab($crontabConfig); 修改后&#…

Electron学习笔记(二)

文章目录 相关笔记笔记说明 三、引入现代前端框架1、配置 webpack&#xff08;1&#xff09;安装 webpack 和 electron-webpack&#xff1a;&#xff08;2&#xff09;自定义入口页面 2、引入 Vue&#xff08;1&#xff09;安装 Vue CLI &#xff08;2&#xff09;调试配置 -- …

陶瓷生产线智能巡检系统

一、背景 在陶瓷生产的过程中&#xff0c;有一个至关重要但又常常被忽视的环节&#xff0c;那就是陶瓷生产炉的巡检。陶瓷生产炉长度一般约为100~200米&#xff0c;中间烧制输送线有大量轴承。需要巡检的点位包括顶层冒火检测&#xff0c;中间滚轴是否内部断轴检测&#xff0c…

流畅的Python阅读笔记

五一快乐的时光总是飞快了&#xff0c;不知多久没有拿起键盘写文章了&#xff0c;最近公司有Python的需求&#xff0c;想着复习下Python吧&#xff0c;然后就买了本Python的书籍 书名&#xff1a; 《流畅的Python》 下面是整理的一个阅读笔记&#xff0c;大家自行查阅&#xf…

Docker部署Metabase

文章目录 Docker安装MetabaseCentOS7安装Docker获取最新的 Docker 镜像启动Metabase容器在Metabase初始化时查看日志访问Metabase Metabase 的 ClickHouse 驱动程序安装环境简介删除容器创建容器下载click house驱动放入驱动重启容器将元数据库连接到 ClickHouse报错解决 Docke…

JAVA链表相关习题2

1.反转一个单链表。 . - 力扣&#xff08;LeetCode&#xff09; //2在1前面 //1在3前面 //ListNode curhead.next //head.nextnull(翻转后头节点变为最后一个节点) // while(cur ! null) { //记录 当前需要翻转节点的下一个节点 ListNode curNext cu…

Python——Fastapi管理平台(打包+优化)

目录 一、配置多个表 1、后端项目改造 2、导包报错——需要修改&#xff08;2个地方&#xff09; 3、启动后端&#xff08;查看是否有问题&#xff09; 4、配置前端 二、打包——成exe文件&#xff08;不包含static文件&#xff09;简单 1、后端修改 2、前端修改 3、运行打包命…

RisingWave基本操作

什么是RisingWave RisingWave 是一款基于 Apache 2.0 协议开源的分布式流数据库。RisingWave 让用户使用操作传统数据库的方式来处理流数据。通过创建实时物化视图&#xff0c;RisingWave 可以让用户轻松编写流计算逻辑&#xff0c;并通过访问物化视图来对流计算结果进行及时、…

|Python新手小白中级教程|第二十三章:列表拓展之——元组

文章目录 前言一、列表复习1.索引、切片2.列表操作字符3.数据结构实践——字典 二、探索元组1.使用索引、切片2.使用__add__((添加元素&#xff0c;添加元素))3.输出元组4.使用转化法删除元组指定元素5.for循环遍历元组 三、元组VS列表1.区别2.元组&#xff08;tuple&#xff0…

vant中van-tabs使用中的小问题

1. 怎么去掉默认选中的效果 van-tabs默认情况下启用第一个标签&#xff0c;实际开发中不满足需求&#xff0c;想要点击后再进行选中 解决办法 首先&#xff0c;在标签组数中&#xff0c;添加一个占位标签在样式中设置首个标签不显示代码如下&#xff1a; //js 实际有意思的…