博客 / 詳情

返回

自動類型推導

c++11中添加了自動推導變量的類型auto,以及decltype表示函數的返回值。

auto

auto可以像別的語言一樣自動推導出變量的實際類型。

在實際中,auto像是一個”佔位符“,使用auto聲明的變量必須要進行初始化,以讓編譯器推導出它的實際類型,在編譯時將auto換成真正的類型。

語法:

auto 變量名 = 變量值

實際使用例子:

#include <iostream>
using namespace std;

int main() {
	//沒有const修飾
	auto x = 3.14; //double
	auto y = 520;  //int
	auto z = 'a';  //char
	//auto nb;       //語法錯誤 
	//auto double nbl; //語法錯誤

	int temp = 110;
	auto* a = &temp; //&temp:int*   auto*:int*     auto:int
	auto b = &temp;  //auto:int*
	auto& c = temp;  //auto:int
	auto d = temp;   //auto:int

	//有const修飾
	int tmp = 250;
	const auto a1 = tmp;  //auto:int   a1:const int
	auto a2 = a1;         //a1不是指針也不是引用       auto:int        a2:int
	const auto& a3 = tmp; //a3:const int&      auto:int
	auto& a4 = a3;        //a3是引用類型       a3:const int&     a4:const int&    auto&:const int&   auto:const int

	auto* pt4 = &a1;      //&a1是地址     a1:const int   pt4:const int*    auto:const int

	system("pause");
	return 0;
}

需要注意的是:

在auto和指針、引用結合在一起時,推導的結果會保留const、volatile關鍵字(volatile表示變量,經常修改的變量)

  • 當變量不是指針或者引用類型時,推導的結果中不會保留const、volatile關鍵字。
  • 當變量時指針或者引用類型時,推導的結果中會保留const、volatile關鍵字。

就如上述代碼中的 //有const修飾 後面的代碼,需要注意變量是否為指針或者引用類型。

auto不能推導的4個情況

1. 不能作為函數參數使用,因為只有在函數調用的時候才會給函數參數傳遞實參,auto要求必須要給修飾的變量賦值,因此二者不矛盾。
int func(auto a, auto b) {  //error
	cout << "a: " << a << ", b: " << b << endl;
}
2.不能用於類的非靜態成員變量的初始化
class Test {
	auto v1 = 0;                  //error
	static auto v2 = 0;           //error,類的靜態非常量成員不允許在類內部直接初始化
	static const auto v3 = 10;    //ok
};
3. 不能使用auto關鍵字定義數組
int func() {
	int array[] = { 1, 2, 3, 4, 5 }; //定義數組
	auto t1 = array;                 //ok, t1被推導為 int* 類型
	auto t2[] = array;               //error, auto無法定義數組
	auto t3[] = { 1, 2, 3, 4, 5 };   //error, auto無法定義數組
}
4. 無法使用auto推導出模板參數
template <typename T>
struct Test {};

int func() {
	Test<double> t;
	Test<auto> t1 = t;     //error,無法推導出模板類型
	return 0;
}

auto常用用途

1. 用於STL容器的遍歷

在遍歷時我們會寫:map<int, string>::iterator it = mp.begin();這樣的迭代器,在有了auto之後可以用auto代替map<int, string>::iterator

#include <iostream>
#include <map>
using namespace std;

int main() {
	//key:int , value:string
	map<int, string>mp;
	mp.insert(make_pair(1, "ace"));
	mp.insert(make_pair(2, "sabo"));
	mp.insert(make_pair(3, "luffy"));
	//map<int, string>::iterator it = mp.begin();
	auto it = mp.begin();
	for (; it != mp.end(); it++) {
		cout << "key: " << it->first << ", value: " << it->second << endl;
	}
	system("pause");
	return 0;
}

如上述代碼,在有了auto之後迭代器的定義簡單方便了很多。

2. 用在泛型編程

在使用模板的時候,很多情況下我們不知道變量應該定義成什麼類型,比如下面的代碼:

#include <iostream>
#include <map>
using namespace std;

class T1 {
public:
	static int get() {
		return 10;
	}
};
class T2 {
public:
	static string get() {
		return "hello, world";
	}
};

//template<class T, typename P>
template <class T>
void func() {
	auto ret = T::get();
	//P ret = T::get();
	cout << "ret: " << ret << endl;
}

int main() {
    func<T1>();
    func<T2>();
	//func<T1, int>();
	//func<T2, string>();
    system("pause");
    return 0;
}

上述代碼中T1::get()返回int類型,T2::get()返回string類型。在模板中調用時就能不確定返回什麼類型的值,使用auto解決了這一問題,如果不用auto只能像註釋中的那樣多定義一個模板參數來確定返回的值時什麼類型。

decltype

C++11增加了decltype關鍵字,它是在編譯器編譯的時候推導出一個表達式的類型;

decltype不需要定義變量,不需要初始化變量也可以推導類型。語法:decltype(表達式)

decltype是"declare type"的縮寫,譯為"聲明類型"。decltype的推導是在編譯時完成的,只是用於推導表達式的類型,並不會計算表達式的值,如下:

int a = 10; 
decltype(a) b = 99;            //a:int            b:int
decltype(a+3.14) c = 3.14159;  //a+3.14:double    c:double
decltype(a+b*c) d = 234.2343;  //a+b*c:double     d:double
decltype(a) e;                 //a:int            e:int 

decltype只是使用了括號中表達式的類型,後面的變量定義和括號中表達式值的大小無關。

auto只能推導已初始化的變量類型,decltype推導的可以不進行初始化。

decltype的推導規則

decltype的3個場景的使用規則:

1. 表達式為普通變量或者普通表達式或者類表達式

表達式為普通變量或者普通表達式後者類表達式時,decltype推導出的類型和表達式的類型是一樣的。

#include <iostream>
using namespace std;

class Test {
public:
	int num = 9;
	string text;
	static const int value = 110;
};
int main() {
	int x = 99;
	const int& y = x;
	decltype(x) a = x;                 //x:int                           a:int    
	decltype(y) b = x;                 //y:const int&                    b:const int&
	decltype(Test::value) c = 0;       //Test::value : const int         c:const int
	Test t;                        
	decltype(t.text) d = "hello, world"; //t.text : string               d:string
    
	system("pause");
	return 0;
}
2.表達式是函數調用

表達式是函數調用的時候,decltype推導出的類型和函數返回值是一致的。

#include <iostream>
using namespace std;

class Test {
public:
	int num = 9;
	string text;
	static const int value = 110;
};
//函數聲明
int func_int() {};
int& func_int_r() {};
int&& func_int_rr() {};
const int func_cint() {};
const int& func_cint_r() {};
const int&& func_cint_rr() {};
const Test func_ctest() {};

int main() {
	int n = 100;
	decltype(func_int()) a = 0;    //func_int():int            a:int
	decltype(func_int_r()) b = n;  //func_int_r():int&         b:int&
	decltype(func_int_rr()) c = 0; //func_int_rr():int&&       c:int&&
	decltype(func_cint()) d = 0;   //func_cint():const int     d:int    (這裏是因為func_cint()函數返回的值是一個純右值,就會被推導為int;對於右值而言只有類類型可以攜帶const、volatile限定符,其他需要忽略這兩個限定符)
	decltype(func_cint_r()) e = 0; //func_cint_r():const int&      e:const int&
	decltype(func_cint_rr()) f = 0; //func_cint_rr():const int&&     f:const int&&
	decltype(func_ctest()) g = Test(); //func_ctest:const Test       g:const Test

	system("pause");
	return 0;
}

上述代碼中func_cint()返回的是一個純右值(再func_cint()運行之後不再存在數據,也就是返回的是臨時性的數據),對於純右值而言,只有類類型會攜帶const、volatile限定符,其他需要忽略這兩個限定符。

上例中用到了int&&等右值引用,這裏補充一下右值引用相關內容(以int&&為例):

int&是左值引用,綁定到可命名、可持久存在的對象;

C++11引入了int&&右值引用

int&&為右值引用,綁定到臨時對象或將要被銷燬的對象,用於移動語義和完美轉發。

int a = 10;
int& b = a;   //正確
int& c = 30;  //錯誤
int&& r = a;  //錯誤
int&& d = 30; //正確
int&& e = 20+10; //正確

int&&的最重要的應用:移動語義

在移動右值的時候可以避免深拷貝,性能更高:

vector<int> a = {1,2,3};
vector<int> b = std::move(a);

其中std::move()是c++11引入的一個函數,它的作用是:把一個對象強制轉換成右值引用,從而觸發移動語義,注意:std::move()本身並不會移動任何數據,它只是一個類型轉換。

#include <iostream>
#include <vector>
using namespace std;

int main() {
 vector<int> a = {1,2,3};

 vector<int> b = std::move(a);

 cout << a.size() << endl; // 可能為0
}

上述代碼中的vector<int> b = std::move(a)發生的是:

a->內存數據

移動後

b->內存數據

a->空

移動之後b接管了a的資源,a變成空對象

移動有別於拷貝,新的變量接管了原來變量的內存數據,原來變量就成為空對象了。

注意:

std::move()不會移動對象。只是説:這個對象的資源可以被拿走了。

真正移動的是:移動構造函數 或者 移動賦值運算符

3.表達式是一個左值,或者被括號()包圍

表達式是一個左值,或者被括號()包圍時,使用decltype推導出的是表達式類型的引用(如果有const、volatile限定符不能忽略)

#include <iostream>
using namespace std;

class Test {
public:
	int num = 9;
	string text;
	static const int value = 110;
};
int main() {
	const Test obj;
	//帶有括號的表達式
	decltype(obj.num) a = 0;    //obj.num:int     a:int
	decltype((obj.num)) b = a;  //obj.num:int     b:const int&       加了括號是引用,這裏就是一個obj.num的引用,obj是const的,obj的裏面的成員就也是const的
	//加法表達式
	int n = 0, m = 0;
	decltype(n + m) c = 0;         //n+m:int        c:int         這裏就是一個普通的表達式
	decltype(n = n + m) d = n;     //n=n+m :int     d:int&        這裏n是一個左值,所以是引用

	system("pause");
	return 0;
}

如上述代碼obj.numint類型,(obj.num)int&類型,所以b是const int&類型

n + m是普通的表達式,是右值,n = n + m這裏的n進行賦值後是左值,所以是int&類型

decltype的常用用途

decltype多引用在泛型編程中,比如我們編寫一個類模板,在裏邊添加遍歷容器的函數,代碼如下:

#include <iostream>
#include <list>
using namespace std;

template<class T>
class Container {
public:
	void print(T& t) {
		for (m_it = t.begin(); m_it != t.end(); m_it++) {
			cout << "value: " << *m_it << endl;
		}
		cout << endl;
	}
private:
	decltype(T().begin()) m_it;            //需要補充內容:T()是T的臨時變量。。。。。。
};
int main() {
	list<int> ls{ 1, 2, 3, 4, 5, 6, 7 };
	Container<list<int>> c;
	c.print(ls);

	const list<int> ls1{ 1, 2, 3, 4, 5, 6, 7 };
	Container<const list<int>> c1;
	c1.print(ls);

	system("pause");
	return 0;
}

如果我們15行寫T::iterator m_it;編譯器會報錯,無法得知這裏要定義一個什麼類型的iterator。

有了decltype就很好的解決了這個問題:decltype(T().begin()) m_it這裏推導出的一定是一個迭代器類型的。

其中T()是T的臨時變量,它在這裏創建一個T類型的臨時對象(默認構造),以便於可以調用begin()函數,從而讓decltype可以推導出迭代器類型。

返回類型後置

在泛型編程中,可能需要通過參數的運算來確定返回值的類型,比如:

#include <iostream>
using namespace std;

template<typename R, typename T, typename U>
R add(T t, U u) {
	return t + u;
}
int main() {
	int x = 520;
	double y = 13.14;
	auto res = add<decltype(x+y),int,double>(x, y);
	cout << "res = " << res << endl;

	system("pause");
	return 0;
}

上述代碼中R是由傳入的闡述 T 和 U 決定的,在調用模板函數時,我們通多decltype(x+y)自動推導函數返回值的類型。

但是在實際開發中,很少能知道add()函數的內部是怎麼樣的,也就無法確定返回值的類型了。

在c++11中增加了返回值類型後置語法,它是將decltype和auto結合起來完成返回類型的推導。語法如下:

//符號 -> 後邊跟隨的是函數返回值的類型
auto func(參數1, 參數2, ...) -> decltype(參數表達式)

其中decltype(參數表達式)中的表達式不需要一定和func()內部的操作有關,只做類型推導的作用。auto會追蹤decltype()推導出的類型。

使用返回值類型後置語法後,之前的案例可以改為:

#include <iostream>
using namespace std;

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u){
	return t + u;
}

int main() {
	int x = 520;
	double y = 13.14;
	auto res = add(x, y);
	cout << "res = " << res << endl;

	system("pause");
	return 0;
}

使用了返回值類型後置後,函數的調用變的更加方便;再加上方法的默認模板參數,這裏的add就可以不需要<>直接傳輸參數add(x,y)

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.