auto與迭代器的用法_C++ auto型別推導完全攻略
技術標籤:auto與迭代器的用法
在C++11 之前的版本(C++98 和 C++ 03)中,定義變數或者宣告變數之前都必須指明它的型別,比如 int、char 等;但是在一些比較靈活的語言中,比如C#、JavaScript、PHP、Python等,程式設計師在定義變數時可以不指明具體的型別,而是讓編譯器(或者直譯器)自己去推導,這就讓程式碼的編寫更加方便。C++11 為了順應這種趨勢也開始支援自動型別推導了!C++11 使用auto關鍵字來支援自動型別推導。
auto 型別推導的語法和規則
在之前的 C++ 版本中,auto 關鍵字用來指明變數的儲存型別,它和 static 關鍵字是相對的。auto 表示變數是自動儲存的,這也是編譯器的預設規則,所以寫不寫都一樣,一般我們也不寫,這使得 auto 關鍵字的存在變得非常雞肋。C++11 賦予 auto 關鍵字新的含義,使用它來做自動型別推導。也就是說,使用了 auto 關鍵字以後,編譯器會在編譯期間自動推匯出變數的型別,這樣我們就不用手動指明變數的資料型別了。auto 關鍵字基本的使用語法如下:
auto name = value;
name 是變數的名字,value 是變數的初始值。注意:auto 僅僅是一個佔位符,在編譯器期間它會被真正的型別所替代。或者說,C++ 中的變數必須是有明確型別的,只是這個型別是由編譯器自己推匯出來的。auto 型別推導的簡單例子:
auto n = 10;
auto f = 12.8;
auto p = &n;
auto url = "http://c.biancheng.net/cplus/";
下面我們來解釋一下:
第 1 行中,10 是一個整數,預設是 int 型別,所以推匯出變數 n 的型別是 int。
第 2 行中,12.8 是一個小數,預設是 double 型別,所以推匯出變數 f 的型別是 double。
第 3 行中,&n 的結果是一個 int* 型別的指標,所以推匯出變數 f 的型別是 int*。
第 4 行中,由雙引號
""
包圍起來的字串是 const char* 型別,所以推匯出變數 url 的型別是 const char*,也即一個常量指標。
我們也可以連續定義多個變數:
int n = 20;
auto *p = &n, m = 99;
先看前面的第一個子表示式,&n 的型別是 int*,編譯器會根據 auto *p 推匯出 auto 為 int。後面的 m 變數自然也為 int 型別,所以把 99 賦值給它也是正確的。這裡我們要注意,推導的時候不能有二義性。在本例中,編譯器根據第一個子表示式已經推匯出 auto 為 int 型別,那麼後面的 m 也只能是 int 型別,如果寫作m=12.5
auto 的高階用法
auto 除了可以獨立使用,還可以和某些具體型別混合使用,這樣 auto 表示的就是“半個”型別,而不是完整的型別。請看下面的程式碼:
int x = 0;auto *p1 = &x; //p1 為 int *,auto 推導為 intauto p2 = &x; //p2 為 int*,auto 推導為 int*auto &r1 = x; //r1 為 int&,auto 推導為 intauto r2 = r1; //r2 為 int,auto 推導為 int
下面我們來解釋一下:
第 2 行程式碼中,p1 為 int* 型別,也即 auto * 為 int *,所以 auto 被推導成了 int 型別。
第 3 行程式碼中,auto 被推導為 int* 型別,前邊的例子也已經演示過了。
第 4 行程式碼中,r1 為 int & 型別,auto 被推導為 int 型別。
第 5 行程式碼是需要重點說明的,r1 本來是 int& 型別,但是 auto 卻被推導為 int 型別,這表明當
=
右邊的表示式是一個引用型別時,auto 會把引用拋棄,直接推匯出它的原始型別。
接下來,我們再來看一下 auto 和 const 的結合:
int x = 0;const auto n = x; //n 為 const int ,auto 被推導為 intauto f = n; //f 為 const int,auto 被推導為 int(const 屬性被拋棄)const auto &r1 = x; //r1 為 const int& 型別,auto 被推導為 intauto &r2 = r1; //r1 為 const int& 型別,auto 被推導為 const int 型別
下面我們來解釋一下:
第 2 行程式碼中,n 為 const int,auto 被推導為 int。
第 3 行程式碼中,n 為 const int 型別,但是 auto 卻被推導為 int 型別,這說明當
=
右邊的表示式帶有 const 屬性時, auto 不會使用 const 屬性,而是直接推匯出 non-const 型別。第 4 行程式碼中,auto 被推導為 int 型別,這個很容易理解,不再贅述。
第 5 行程式碼中,r1 是 const int & 型別,auto 也被推導為 const int 型別,這說明當 const 和引用結合時,auto 的推導將保留表示式的 const 型別。
最後我們來簡單總結一下 auto 與 const 結合的用法:
當型別不為引用時,auto 的推導結果將不保留表示式的 const 屬性;
當型別為引用時,auto 的推導結果將保留表示式的 const 屬性。
auto 的限制
前面介紹推導規則的時候我們說過,使用 auto 的時候必須對變數進行初始化,這是 auto 的限制之一。那麼,除此以外,auto 還有哪些其它的限制呢?1) auto 不能在函式的引數中使用。這個應該很容易理解,我們在定義函式的時候只是對引數進行了宣告,指明瞭引數的型別,但並沒有給它賦值,只有在實際呼叫函式的時候才會給引數賦值;而 auto 要求必須對變數進行初始化,所以這是矛盾的。2) auto 不能作用於類的非靜態成員變數(也就是沒有 static 關鍵字修飾的成員變數)中。3) auto 關鍵字不能定義陣列,比如下面的例子就是錯誤的:
char url[] = "http://c.biancheng.net/";
auto str[] = url; //arr 為陣列,所以不能使用 auto
4) auto 不能作用於模板引數,請看下面的例子:
template <typename T>class A{ //TODO:};int main(){ A<int> C1; A<auto> C2 = C1; //錯誤 return 0;}
auto 的應用
說了那麼多 auto 的推導規則和一些注意事項,那麼 auto 在實際開發中到底有什麼應用呢?下面我們列舉兩個典型的應用場景。
使用 auto 定義迭代器
auto 的一個典型應用場景是用來定義 stl 的迭代器。我們在使用 stl 容器的時候,需要使用迭代器來遍歷容器裡面的元素;不同容器的迭代器有不同的型別,在定義迭代器時必須指明。而迭代器的型別有時候比較複雜,書寫起來很麻煩,請看下面的例子:
#include using namespace std;int main(){ vector< vector<int> > v; vector< vector<int> >::iterator i = v.begin(); return 0;}
可以看出來,定義迭代器 i 的時候,型別書寫比較冗長,容易出錯。然而有了 auto 型別推導,我們大可不必這樣,只寫一個 auto 即可。
修改上面的程式碼,使之變得更加簡潔:
#include using namespace std;int main(){ vector< vector<int> > v; auto i = v.begin(); //使用 auto 代替具體的型別 return 0;}
auto 可以根據表示式 v.begin() 的型別(begin() 函式的返回值型別)來推匯出變數 i 的型別。
auto 用於泛型程式設計
auto 的另一個應用就是當我們不知道變數是什麼型別,或者不希望指明具體型別的時候,比如泛型程式設計中。我們接著看例子:
#include using namespace std;class A{public: static int get(void){ return 100; }};class B{public: static const char* get(void){ return "http://c.biancheng.net/cplus/"; }};template <typename T>void func(void){ auto val = T::get(); cout << val << endl;}int main(void){ func(); func(); return 0;}
執行結果:
100
http://c.biancheng.net/cplus/
本例中的模板函式 func() 會呼叫所有類的靜態函式 get(),並對它的返回值做統一處理,但是 get() 的返回值型別並不一樣,而且不能自動轉換。這種要求在以前的 C++ 版本中實現起來非常的麻煩,需要額外增加一個模板引數,並在呼叫時手動給該模板引數賦值,用以指明變數 val 的型別。但是有了 auto 型別自動推導,編譯器就根據 get() 的返回值自己推匯出 val 變數的型別,就不用再增加一個模板引數了。下面的程式碼演示了不使用 auto 的解決辦法:
#include using namespace std;class A{public: static int get(void){ return 100; }};class B{public: static const char* get(void){ return "http://c.biancheng.net/cplus/"; }};template <typename T1, typename T2> //額外增加一個模板引數 T2void func(void){ T2 val = T1::get(); cout << val << endl;}int main(void){ //呼叫時也要手動給模板引數賦值 funcint>(); funcconst return 0;}