C++11 类型推导 auto

静态类型和动态类型的区别

  • 静态类型的类型检查主要发生在编译阶段
  • 动态类型的类型检查主要发生在运行阶段

关键字auto

传统auto是一个存储类型指示符(storage-class-specifier),使用auto修饰的变量是具有自动存储期的局部变量,而C++11中auto是作为一个新的类型指示符(type-specifier),它声明的变量的类型必须由编译器在编译时期推导而得。

int main() 
{
    double foo();
    auto x = 1;      // x的类型为int
    auto y = foo();  // y的类型为double
    struct m { int i; }str;
    auto str1 = str;    // str1的类型是struct m
    
    auto z;     // 无法推导,无法通过编译
    z = x;
}

值得注意的是变量z,这里我们使用auto关键字来“声明”z,但不立即对其进行定义,此时编译器则会报错。这跟通过其他关键字先声明后定义变量的使用规则不同,auto声明的变量必须被初始化,以使编译器能够从其初始化表达式中推导出其类型。从这个意义上来讲,auto并非一种“类型”声明,而是一个类型声明时的“占位符”,编译器在编译时期会将auto替代为变量实际的类型。

auto优势

  • 在拥有初始化表达式的复杂类型变量声明时简化代码

      void loopover(std::vector<std::string> & vs)
       {
          std::vector<std::string>::iterator i = vs.begin(); // iterator,想说爱你并不容易
    
          for (; i < vs.end(); i++) {
              // 一些代码
          }
      }
    

    以上代码可以优化为:

      void loopover(std::vector<std::string> & vs) {
      
          for (auto i = vs.begin(); i < vs.end(); i++) {
              // 一些代码
          }
      }
    
  • 可以免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误

  • 其“自适应”性能能够在一定程度上支持泛型的编程

      template<typename T1, typename T2>
      double Sum(T1 & t1, T2 & t2) {
          auto s = t1 + t2;   // s的类型会在模板实例化时被推导出来
          return s; 
      } 
      
      int main() 
      {
          int a = 3;
          long b = 5;
          float c = 1.0f, d = 2.3f;
          
          auto e = Sum(a, b); // s的类型被推导为long
          auto f = Sum(c, d); // s的类型被推导为float
      }
    
  • auto还会在一些情况下取得意想不到的好效果

      #define Max1(a, b) ((a) > (b)) ? (a) : (b)
      #define Max2(a, b) ({ \
              auto _a = (a); \
              auto _b = (b); \
              (_a > _b) ? _a: _b; })
      
      int main() {
          int m1 = Max1(1*2*3*4, 5+6+7+8);
          int m2 = Max2(1*2*3*4, 5+6+7+8);
      }
    

    相比传统的三元运算符表达式,Max2中将a和b都先算出来,再使用三元运算符进行比较,提高了计算性能。

auto使用细则

  • auto类型指示符与指针和引用之间的关系

      int x;
      int * y = &x;
      double foo();
      int & bar();
      
      auto * a = &x;      // int*
      auto & b = x;       // int&
      auto c = y;         // int*
      auto * d = y;       // int*
      auto * e = &foo();  // 编译失败, 指针不能指向一个临时变量
      auto & f = foo();   // 编译失败, nonconst的左值引用不能和一个临时变量绑定
      auto g = bar();     // int
      auto & h = bar();   // int&
    

    上文中可以看出,对于a,c,d三个变量而言,声明其为auto *或auto并没有区别,而如果要使得auto声明的变量是另一个变量的引用,则必须要使用auto&,如同本例中的变量b和h一样。

  • auto与volatile和const之间也存在着一些相互的联系
    C++11 标准规定auto可以与cv限制符一起使用,不过声明为auto的变量并不能从其初始化表达式总“带走”cv限制符。

      double foo();
      float * bar();
      
      const auto a = foo();       // a: const double
      const auto & b = foo();     // b: const double&
      volatile auto * c = bar();  // c: volatile float*
      
      auto d = a;                 // d: double
      auto & e = a;               // e: const double &
      auto f = c;                 // f: float *
      volatile auto & g = c;      // g: volatile float * &
    

    其中变量d,f却无法带走a和f的常量性或者易失性。不过声明为引用的变量e,g都保持了其引用的对象相同的属性(事实上,指针也是一样的)。

auto无法使用的情况

auto受制于语法的二义性,或者是实现的困难性,auto往往也有使用上的限制。

	void fun(auto x =1){}  // 1: auto函数参数,无法通过编译
	
	struct str{
	    auto var = 10;   // 2: auto非静态成员变量,无法通过编译
	};
	
	int main() {
	    char x[3];
	    auto y = x;
	    auto z[3] = x; // 3: auto数组,无法通过编译
	
	    // 4: auto模板参数(实例化时),无法通过编译
	    vector<auto> x = {1};
	}
  1. 从fun函数可以看出,auto是不能做形参的类型的
  2. 编译器阻止auto对结构体中的非静态成员进行推导,即使成员拥有初始值
  3. 声明auto数组。x是一个数组,y的类型是可以推导的,是一个char数组类型,而声明auto z[3]这样的数组同样会被编译器禁止
  4. 在实例化模板的时候使用auto作为模板参数,虽然可以一眼而知是int类型,但编译器却阻止了编译
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章