auto (C++)
auto是C++程式語言的關鍵字。自C++11以來,auto關鍵字用於兩種情況:聲明變數時根據初始化表達式自動推斷該變數的類型、聲明函數時函數返回值的預留位置。C++98標準中auto關鍵字用於自動變數的聲明,但由於使用極少且多餘,在C++11中已刪除這一用法。[1][2]
簡要理解
auto可以在聲明變數時根據變數初始值的類型自動為此變數選擇匹配的類型。C++語言類似的關鍵字還有decltype。
舉例:對於值x=1;即可以聲明:int x = 1或long x = 1,也可以直接聲明auto x = 1。
其它語言的類似功能包括C#的var關鍵字。
用法
根據初始化表達式自動推斷被聲明的變數的類型,如:
auto f=3.14; //double auto s("hello"); //const char* auto z = new auto(9); // int* auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型
但是,這麼簡單的變數聲明類型,不建議用auto關鍵字,而是應更清晰地直接寫出其類型。
auto關鍵字更適用於類型冗長複雜、變數使用範圍專一時,使程式更清晰易讀:
std::vector<int> vect; for(auto it = vect.begin(); it != vect.end(); ++it) { //it的类型是std::vector<int>::iterator std::cin >> *it; }
或者儲存lambda表達式類型的變數聲明:
auto ptr = [](double x){return x*x;};//类型为std::function<double(double)>函数对象
在模板函數定義時,如果變數的類型依賴於模板參數,使用auto關鍵字使得在編譯期確定這些類型,如:
template <class T, class U>void Multiply(T t, U u) { auto v = t * u; std::cout<<v; }
模板函數的返回類型如果也是依賴於從模板參數推導,
template <typename _Tx, typename _Ty> auto multiply(_Tx v1, _Ty v2) -> decltype( _Tx * _Ty ) { return v1*v2; } auto result = multiply(101, 1.414); // 结果类型是double
語意
使用auto關鍵字做類型自動推導時,依次施加以下規則:[參1]
- 首先,如果初始化表達式是參照,首先去除參照;
- 上一步後,如果剩下的初始化表達式有頂層的const且/或volatile限定符,去除掉。
這一組規則同於模板函數的模板參數推導(template argument deduction)時的規則。但auto關鍵字可以從C++11風格的花括號{與}包圍的值列表推導出std::initializer_list;而模板函數的形參推導時不認為這種值列表是一個類型,因此不能由值列表推導出std::initializer_list類型。
因而,使用auto關鍵字聲明變數的類型,不能自動推導出頂層的CV-qualifiers,也不能自動推導出參照類型,需要顯式指定。例如:
const int v1 = 101; auto v2 = v1; // v2类型是int,脱去初始化表达式的顶层const v2=102; // 可赋值 auto al = { 10, 11, 12 };//类型是std::initializer_list<int> template<class T> void foo(T arg); // 函数模板声明 foo(v2); //函数模板实例化为 void foo<int>(int)
如果需要具有頂層的CV-qualifiers,或者參照的類型,解決辦法是顯式指明:
const auto& v3=v1; foo<const int&>(v1);//直接指明模板参数类型 template<class T> void foo(const T& arg);//或者偏特化模板函数
如果auto關鍵字還帶上&號,聲明參照類型,則不執行const剝除(const-stripping),例如:
const int c = 0; auto& rc = c; rc = 44; // 编译错误,const int类型
這是因為如果不抑制const剝除,則得到了一個非常數參照型變數,指向了const變數,這顯然是不可接受的。模板參數推導也遵循此規則。
初始化表達式為陣列,auto關鍵字推導的類型為指標。這是因為陣列名在初始化表達式中自動隱式轉換為首元素地址的右值。例如:
int a[9]; auto j = a; std::cout << typeid(j).name() << " "<<sizeof(j)<<" "<<sizeof(a)<< std::endl;
由於C++規定字串字面量是左值,因此可以通過&運算子直接取地址:
auto al = &"hello"; // a1的类型是const char(*) [6]
auto關鍵字的類型完美轉發
C++11使用auto聲明變數時,如:auto&& var=initValue;
「auto&&」並不意味着這一定是右值參照類型的變數,而是類似於模板函數參數的類型推導,既可能是左值參照,也可能是右值參照。其目的是把初始化表達式的值分類情況,完美轉發給由auto聲明的變數。也即:
- 如果初始化值(initializer)是類型A的左值,則聲明的變數類型為左值參照A&;
- 如果初始化值是類型A的右值,則聲明的變數類型為右值參照A&&。
#include<iostream>
#include <vector>
#include <typeinfo>
using namespace std;
struct Widget{};
Widget makeWidget(){ return Widget(); } // 类工厂函数
int main()
{
Widget&& var1 = Widget(); // var1的类型是右值引用,但是作为左值
auto&& var2 = var1; //var2的类型是左值引用
std::vector<int> v = { 1, 2, 3 };
auto&& val = v[0]; // std::vector::operator[]的返回值是元素左值,所以val的类型是左值引用
Widget&& var3 = makeWidget(); // var3是左值,但它的类型是右值引用
Widget var4 = static_cast<Widget&&>(var1); // var4是左值,但它的类型是右值引用
std::cout << typeid(var1).name() << std::endl;
std::cout << typeid(var2).name() << std::endl;
std::cout << typeid(val).name() << std::endl;
std::cout << typeid(var3).name() << std::endl;
std::cout << typeid(var4).name() << std::endl;
}