自动类型推导:auto
在本节课中,我们将学习 C++ 如何自动推断变量的数据类型。
C++11引入的一个最佳特性是自动类型推导。
auto关键字可用于让编译器自行决定数据类型。
我们不再需要显式定义数据类型,这在处理复杂的模板表达式时尤其有帮助。
1#include <iostream> 2 3int main() { 4 auto myStr = "PageFlux"; 5 auto myDoub = 3.14; 6 auto myInt = 3; 7 std::cout << myStr << std::endl; 8 std::cout << myDoub << std::endl; 9 std::cout << myInt << std::endl; 10}
主要特性
- 使用了自动函数模板参数推导的技术。
- 在复杂的模板表达式中非常有用。
- 允许我们处理未知类型:
1auto func = []{ return 5; };
上述语句中使用了lambda函数,可点击此处了解更多信息。
在与初始化列表结合使用时需谨慎:
1auto myInt{2011}; 2auto myInt2 = {2011};
以下代码对比了显式类型定义与自动类型推导:
1#include <vector> 2 3int myAdd(int a, int b) { return a + b; } 4 5int main() { 6 // 定义一个整数值 7 int i = 5; // 显式 8 auto i1 = 5; // auto 9 10 // 定义一个整数引用 11 int& b = i; // 显式 12 auto& b1 = i; // auto 13 14 // 定义一个函数指针 15 int (*add)(int, int) = myAdd; // 显式 16 auto add1 = myAdd; // auto 17 18 // 遍历一个向量 19 std::vector<int> vec; 20 for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {} 21 for (auto it1 = vec.begin(); it1 != vec.end(); ++it1) {} 22}
C++ Insights 帮助我们可视化编译器推导出的类型。该工具作者Andreas Fertig撰写了几篇关于auto的博客文章。
自动初始化
auto根据初始化表达式来确定其类型。这意味着:如果没有初始化表达式,就无法确定类型,也就无法创建变量。换句话说,编译器确保每个变量都经过初始化。这是auto带来的一个很少被提及的良好副作用。
无论是因为忘记初始化,还是因为对语言理解不清而未初始化,结果都是相同的:未定义行为。使用auto可以避免这些棘手的错误。
我们是否掌握了所有变量初始化规则?如果掌握,请恭喜;如果没有,请阅读文章默认初始化及其链接的所有相关文章。
文中提到:“具有自动存储期的对象(及其子对象)会被初始化为不确定值”。这种优化实际上弊大于利。非用户自定义的局部变量不会进行默认初始化。
我修改了第二个程序,使其采用默认初始化,以更明显地展示未定义行为。
未定义行为
1// init.cpp 2 3#include <iostream> 4#include <string> 5 6struct T1 {}; 7 8struct T2 { 9 int mem; // 不正确:值为不确定 10public: 11 T2() {} 12}; 13 14int n; // 正确:初始化为0 15 16int main() { 17 std::cout << std::endl; 18 19 int n; // 不正确:值为不确定 20 std::string s; // 正确:调用默认构造函数;初始化为空字符串 21 T1 t1; // 正确:调用默认构造函数 22 T2 t2; // 正确:调用默认构造函数 23 24 std::cout << "::n " << ::n << std::endl; 25 std::cout << "n: " << n << std::endl; 26 std::cout << "s: " << s << std::endl; 27 std::cout << "T2().mem: " << T2().mem << std::endl; 28 29 std::cout << std::endl; 30}
首先,第24行中的作用域解析运算符::用于访问全局作用域。在此例中,指的是第14行的全局变量n。
有趣的是,第19行的自动变量n的值为0。但n的值实际上是未定义的,因此程序存在未定义行为。同样,类T2的成员mem也返回未定义值。
使用auto
现在,我们将程序改写为使用auto。
1// initAuto.cpp 2 3#include <iostream> 4#include <string> 5 6struct T1 {}; 7 8struct T2 { 9 int mem = 0; // auto mem = 0 是错误的 10public: 11 T2() {} 12}; 13 14auto n = 0; 15 16int main() { 17 std::cout << std::endl; 18 19 using namespace std::string_literals; 20 21 auto n = 0; 22 auto s = ""s; 23 auto t1 = T1(); 24 auto t2 = T2(); 25 26 std::cout << "::n " << ::n << std::endl; 27 std::cout << "n: " << n << std::endl; 28 std::cout << "s: " << s << std::endl; 29 std::cout << "T2().mem: " << T2().mem << std::endl; 30 31 std::cout << std::endl; 32}
源代码中有两行特别值得注意。第一,第9行指出,当前标准禁止使用auto初始化类的非常量成员。因此必须显式指定类型。这一设计看似反直觉。关于此问题的C++标准化委员会讨论见:文章。
第二,如第23行所示,C++14支持字符串字面量。我们通过在C风格字符串字面量""后添加后缀s(即""s)来构建。为了方便,我们在第20行导入了该命名空间:using namespace std::string_literals。
程序输出并不引人注目,仅作完整性说明。T2().mem的值为0。
更多信息
在下一课中,我们将学习如何使用auto重构代码。