静态类型、动态类型与类型推导
在聊auto
类型推导前,我们先理解一下静态类型和动态类型吧。
我想对于C/C++
程序员而言,静态类型应该都不陌生,因为C/C++
语言所有的变量必须遵循先定义后使用。
而python语言就是典型的动态类型,先用即用。
例如下面代码:
a = 22 print("a:", a)
对于编译器/解释器来讲,静态类型是在编译时期进行类型检查,动态类型则是在运行时期对类型检查。
运行时期对类型检查这种功能我们就称之为类型推导。
auto的历史变更
在早期的C++中,auto的功能是具有自动存储期的局部变量,但是因为声明变量时默认都是自动存储期的局部变量,所以auto就显得很鸡肋,为此在C++11标准委员会就将auto重新定义了。由一个存储类型指示符(storage-class-specifier,如 static、extern等)变成了新的类型指示符(type-specifier,如int、float等)。
注意:auto声明的变量类型必须由编译器在编译时期推导得出,这会导致通过auto定义的变量必须赋予初值。见下图
auto类型推导的用法还是蛮简单的,这里我写了一个简单的例子。
#include <iostream> int main() { auto a = 111; auto b = 1.f; auto c = a + b; return 0; }
auto的优势
auto这个关键字在实际开发中使用还是蛮频繁的(指C++11的auto)。所以对于一个经常使用STL的程序员简直就是福音,因为这能极大的改善声明变量类型的长度,使之简单化。
auto-自动类型推导
例如下面这个,对于一个map进行迭代器遍历,我们定义迭代器变量的类型就需要是std::map<int, std::string>::iterator
,这绝对是所有程序员不想看见的,太冗余了。
#include <iostream> #include <map> #include <iterator> #include <string> int main() { std::map<int, std::string> mm; mm.insert(std::make_pair(1, "张三")); mm.insert(std::make_pair(2, "李四")); for (std::map<int, std::string>::iterator iter = mm.begin(); iter != mm.end(); ++iter) { std::cout << iter->first << "\t" << iter->second << std::endl; } return 0; }
那么我们就可以用auto来优化。 这样的代码又简洁又易读。
#include <iostream> #include <map> #include <iterator> #include <string> int main() { std::map<int, std::string> mm; mm.insert(std::make_pair(1, "张三")); mm.insert(std::make_pair(2, "李四")); for (auto iter = mm.begin(); iter != mm.end(); ++iter) { std::cout << iter->first << "\t" << iter->second << std::endl; } return 0; }
auto还可以用在类型计算上,编译器会为你计算出最为适合的类型
#include <iostream> int main() { int a = 3; auto b = a * 1.1f; auto c = a * 1.1; return 0; }
但auto
也不是万能的,在对于极端的精度计算时,auto
也无能为力。
#include <iostream> int main() { unsigned int a = UINT_MAX; unsigned int b = 1; auto c = a + b; std::cout << c << std::endl; // 0 return 0; }
虽然a+b
会导致数值溢出问题,但是a+b
的返回值是unsigned int
所以auto
并不能帮你自动扩展。
auto最大的优点在于”自适应“可应用于模板中
在下面这个例子中,Sum
函数接受类型T1、T2的俩个变量,模板实例化时才可以确定类型。所以在Sum方法中的s变量的类型随着我们对模板的实例化不同它的类型也会不同。
#include <iostream> template<typename T1, typename T2> double Sum(T1 t1, T2 t2) { auto s = t1 + t2; return s; } int main() { auto a = Sum<int, double>(1, 1.2); auto b = Sum<int, int>(2, 6); return 0; }
模板实例化为Sum<int, double>
时,s的变量类型为double
。
模板实例化为Sum<int, int>
时,s的变量类型为int
。
auto在其他场景的使用与限制
auto可引用于其他场合
- 可用于初始化列表
- 可用于new关键字在堆区创建变量
#include <iostream> int main() { auto b{ 1 }; // 可用于初始化列表 auto c = new auto(1); // 可用于new关键字在堆区创建变量 return 0; }
auto不能使用的场合
- 不能用于函数参数
- 不能用于非静态成员变量
- 不能用于定义数组
- 不能当作模板参数
#include <vector> void Fun(auto x = 1) // 1:auto函数参数 无法通过编译 { } class Test { public: auto var = 10; // 2:auto非静态成员变量 无法通过编译 }; int main() { auto arr[3] = { 1,2,3 }; // 3:auto数组 无法通过编译 std::vector<auto> arr2; // 4:auto模板参数 无法通过编译 return 0; }
对于函数 fun来说,auto不能是其形参类型。你可能感觉对于fun来说,由于其有默认参数,所以应该推导fun形参x的类型为int型。但事实却无法符合大家的想象。因为auto是不能做形参的类型的。如果程序员需要泛型的参数,还是需要求助于模板。
对于结构体/类来说,非静态成员变量的类型不能是 auto的。同样的,由于var定义了初始值,你可能认为auto可以推导str成员var的类型为int 的。但编译器阻止 auto对结构体/类中的非静态成员进行推导,即使成员拥有初始值。
声明auto数组。我们可以看到,main中的声明auto arr[3]这样的数组同样会被编译器禁止。
在实例化模板的时候使用auto作为模板参数,如main中我们声明的 vector<auto> v。你可能认为这里一眼而知是int类型,但编译器却阻止了编译。
以上4种情况的特点基本相似,我们其实可以很容易能够推导出auto所在位置应有的类型,但现有的C++11的标准还没有支持这样的使用方式。