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類型,但編譯器卻阻止了編譯
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章