zoukankan      html  css  js  c++  java
  • auto类型推导

    引言

    auto : 类型推导. 在使用c++的时候会经常使用, 就像在考虑STL时迭代器类型, 写模板的时候使用auto能少写代码, 也能帮助我们避免一些隐患的细节.

    auto初始化

    1. 使用auto型别推导要求必须在定义时初始化, 毕竟需要根据对象的类型推导左值对象的型别.
    auto j; 	// error. 必须初始化
    auto i = 0; // i 推导型别为 int
    vector<int> v; 
    auto vv = v.cbegin();	// vv  推导型别为 const int*
    
    1. 但是auto型别推导会忽略引用和顶层const, 所以要对对象加上想要的修饰.
    const int ci = 0;
    auto i = ci;	// i 推导型别为 int, 忽略了顶层const
    int &ri = i;
    auto ii = ri; 	//ii 推导型别为 int, 忽略了引用
    
    1. C11之前只能通过()=对变量初始化, C++11增加了对定义的对象初始化的方法,可以使用{}对变量初始化

    c11之前的初始化方法

    int i(0);	// i 初始化 0
    int j = 0; 	// j 初始化 0
    

    c11后的初始化方法

    auto i(0); auto j = i;	// 支持c11前
    auto ii{0};	// 使用 {} 进行初始化, 但是auto推导只能接受一个参数
    auto jj = { 0 };	// jj 的推导型别为 initializer_list<int>型别
    

    上面jj的推导居然不是int型别, 而是 initializer_list<int> , 这不能怪auto推导出问题, 这主要是后者的对象初始化就是使用={}, 可以说是auto推导的是最精确的型别. 不管新添的初始化方法, 找一个习惯的就行了.

    auto与for

    auto最常见的就是与for联用, 特别是类型特别复杂的时候. 但是auto又有多种选择, 如 : auto, auto &等, 不同的选择其效率也不一样.

    1. auto , 即 for(auto i:range) . 这使range中的每一个元素都会产生一个副本, 所以即使修改了 i 也不会实际影响到range.
    2. const auto, 及for(const auto i : range). 这也会是range的每一个元素产生一个副本, 但是这个副本竟不能被修改.
    3. auto &, 即for(auto &i : range). 引用, 因为i 直接引用range里面的元素, 所以并不会产生一个副本, 但是 i 的修改也会影响range里元素的值. 通常我们需要修改range是会考虑用到.
    4. const auto&, 即for(const auto &&i : range). i 直接引用range里面的元素, 所以并不会产生一个副本, 并且i 也不能修改. 一般初始化的是一个左值时而且是读取range里的元素时都是用const auto&而不用auto, 因为前者不会产生副本, 效率要高. 当然一般初始化的是一个左值时效率低, 但是如果是右值还是使用const auto效率高, 因为const auto &需要把 i 存储在内存中的一个位置,间接访问会更消耗时间
    5. auto&&, 即for(auto &&i : range). 如果初始化是左值, 那么 i 就是左值引用, 如果初始化是右值, 那么 i 就是右值引用,

    还有const auto &, 当然具体的选择还是看具体的情况而定.

    最后, 当用auto推导多维数组的时, 保证除最内层循环外, 其他的外层循环都应该是引用类型, 否则很容易出错, 即 :

    int a[10][10][10];
    for (const auto &i : a)
        for(const auto &j : i)
            for(const auto k : j)
                ;
    

    最好使用auto型别推导

    1. 初始化

    • 在定义对象的时候可能或多或少会忘记对变量进行初始化, 但当我们使用该变量的时候就会出错, 而且问题也不好找出来, 但是使用auto定义对象就要求必须初始化有时还能减少代码量, 上面我们已经分析过了.
    • 使用auto初始化在平台上还有一点好处, 比如 :
    vector<int> v;
    unsigned size = v.size();	// size()返回size_t型别
    auto sizet = v.size();
    

    ​ 在32的平台unsigned代表的是32位, size_t是32位, 在64的平台unsigned代表的也是23位, 但是size_t却是64位, 这样平台差异可能就会带来问题, 使用auto代替就没有这样的问题.

    不过只有这几点可能不会让人心动, 下面我们还有auto的好处.

    2. STL使用型别推导

    还记得在前言中个说过调用STL最好使用auto推导型别, 如果你还记得mappair 吗? 是这样 map<pair<key, type>>? 还是map<pair<const key, type>>? 答案是最后一种, 那么现在我们就来分析的使用auto推导还是显示型别比较好.

    int main()
    {
    	std::map<string, std::function<type(type, type)>>func = {
    		{ "+", [](auto i, auto j)->auto {return i + j; } },
    		{ "-", [](auto i, auto j)->auto {return i - j; } },
    		{ "*", [](auto i, auto j)->auto {return i * j; } },
    		{ "/", [](auto i, auto j)->auto {return i / j; } }
    	};
    
    	for (const auto &i : func) ;
    
    	for(const std::pair<string, std::function<type(type, type)>> &pa : func) ;
    
    	system("pause");
    	exit(EXIT_SUCCESS);
    }
    

    看到上面的例子毫无问题, 但是深究起来显示型别还是些不完美. 我们知道map的key不能被改变, 所以显示型别的string与map的const string不是匹配, 编译器就会将map对象都会产生一个临时对象再隐式的转为string, 等等. 是不是注意到有一点了, 为了型别匹配赋值会产生临时变量, 那岂不是每一循环都会产生一个临时变量, 但是auto型别推导就是精确匹配的, 不会产生临时变量.

    可能觉得将显示型别的key改为const string就能解决这个问题了, 确实是这样, 但是如果没有注意到这一点细节, 那就会损失效率了, 使用auto可以完全不想这些问题啊.

    当然使用显示型别还是型推导看实际也看个人, 不是必要.

    auto与函数返回类型

    auto不能被声明为返回值,auto不能作为形参,auto不能被修饰为模板参数. 那么这里auto还能怎么和函数关联起来? 能.

    auto放在函数名前面告诉编译器,真正的返回值在函数声明之后. 简单说auto可以作为返回值占位符来使返回值后置.

    就像这样来写.

    auto Return(std::size_t N) -> std::size_t
    {
    	return N;
    }
    

    既然c++规定可以这样写肯定有其意义. 其实这个写法主要用于template中, 当返回值的类型是一个模板类型时使用, 而返回值类型通过decltype来推导.

    这里就解释一下decltype的简单运用. , decltype也是类似与auto的关键字, 都能够进行参数类型推导, 但是decltype必须要接受一个参数, 如下:

    int i = 1;
    decltype(i) j = 1;
    

    auto与模板函数连用时用模板参数作为返回值. 因为编译器并不能直接推断出返回值为类型参数的实际类型, 所以在STL中采用traits编程解决这个问题, 这里时另一种实现方法.

    首先看一个错误的例子:

    template<class T1, class T2, class T3>
        T3 fun(T1 t1, T2 t2) {...}
    

    T3的类型要在函数返回的时候才能知道, 而函数这样写就必须要编译期间就要知道返回值类型. 所以编译器会报错.

    以下这样写就是正确的, 但是必须保证编译器能推导出类型.

    template<class T1, class T2, class T3>
        T1 fun(T1 t1, T3 t3) {...}
    

    使用auto将返回值类型放在最后, 就是告诉编译器真正的返回值在编译后动态获取, 而auto在这里的作用也称为返回值占位

    template<class T1, class T2>
        auto fun(T1 t1, T2, t2) -> decltype(*t1) {...}
    

    以上可以将返回类型放在函数尾做尾置是C11中的要求, 但是C14已经可以将返回型别放推导在函数头. 如 :

    template<class T1>
    decltype(auto)fun() {...}	// 这样的写法同上式一样
    

    虽然规定能够这样写, 有时为了兼容也还是写成尾置.

    auto与new运算符

    我们可以使用auto来推断出new对象的类型, 但是局限在于, 必须对new出来的对象进行单一的初始化.

    auto i = new int; // 这中写法根本没有用到auto的推导哦, 因为new的类型已经确定了
    
    auto i = new auto(1);		// 这里就是用到了auto推导
    auto size = new auto;		// error, 不能推导出size的类型
    auto j = new auto(1,2); 	// error, 只能接收一个初始化值
    

    const中我们分析到顶层const会被忽略, 所以auto是无法推断出顶层const, 即 :

    auto i = new const auto(1); 	// 这里auto并没有推导出顶层const, 所以i的类型实际上是int
    const auto j = new const auto(1);	// 只有显示的定义j的类型是const
    

    如果想直接推导出顶层const的话, 最好还是decltype进行推导.

    注意 : auto推导只能推导出int, double等, 不能推导出short类型.

    总结

    本节对C11的auto用法做了一个浅显的分析, 分别对使用auto的好处, 定义时注意{}对象也必须初始化, auto在与for连用的时候要根据实际参数确定选择哪种实现, 这样效率才会达到最大, 当然一般都使用const auto&auto&&. 最后还对auto与函数返回值关联, 可以将返回型别放在函数名尾也可以, 这样的做法一般在模板中将模板参数作为返回值才考虑用, 平时也不必这样定义函数.

    参考 :

    << Effective Modern C++ >>

    auto, auto&, const auto&以及其它形式的auto变种在for-range loop的选择

  • 相关阅读:
    git(1)-git关联GitHub-windows-转载
    jenkins(4)-jenkins配置邮件通知
    jenkins(3)-linux下安装jenkins(yum install方式)
    【PAT甲级】1090 Highest Price in Supply Chain (25 分)(DFS)
    【PAT甲级】1087 All Roads Lead to Rome (30 分)(MAP【int,string】,邻接表,DFS,模拟,SPFA)
    【PAT甲级】1018 Public Bike Management (30 分)(DFS,SPFA)
    Educational Codeforces Round 61 (Rated for Div. 2) G(线段树,单调栈)
    Atcoder Grand Contest 032C(欧拉回路,DFS判环)
    Educational Codeforces Round 62 (Rated for Div. 2)E(染色DP,构造,思维,组合数学)
    Atcoder Grand Contest 031C(构造,思维,异或,DFS)
  • 原文地址:https://www.cnblogs.com/0xfffffff0/p/10285472.html
Copyright © 2011-2022 走看看