天天看點

C++深度解析(46)—深入了解函數模闆

1.函數模闆深入了解

  • 編譯器從函數模闆通過具體類型産生不同的函數
  • 編譯器會對函數模闆進行兩次編譯 
    • 在聲明的地方對模闆代碼本身進行編譯
    • 對參數替換後的代碼進行編譯(編譯時機:可能發生在定義對象或函數調用時)
  • 注意事項:
    • 函數模闆本身不允許隐式類型轉換 
    • 自動推導類型時,必須嚴格比對
    • 顯示類型指定時,能夠進行隐式類型轉換
  • 程式設計實驗:隐式類型轉換問題 
#include <iostream>

using namespace std;

template <typename T>
T Max(T a, T b)   // 模闆函數,求兩個數的最大值
{
	return a > b ? a : b; // 直接傳回值,傳回對象,就像拷貝了一份傳回去一樣,傳回引用是本身。
}

int main()
{
	int i = 2;
	int j = 3;

	// 自動推導類型:
	cout << Max(i, j) << endl;

	//錯誤,兩個參數類型不同,函數模闆不允許隐式類型轉換。
	//cout << Max('a', 3) << endl;

	// 正确,顯示指定T為int類型,可将'a'隐式轉為指定的int類型,指定傳回值類型。
	cout << Max<int>('a', 3) << endl;

	system("pause");

	return 0;
}
           
  • 運作結果:
    C++深度解析(46)—深入了解函數模闆
  • 程式設計實驗:函數模闆的本質
#include <iostream>
#include <string>

using namespace std;

class Test
{
	Test(const Test &) // 隻要發生對象的拷貝,就出錯,private的
	{

	}

public:
	Test()
	{

	}
};

template <typename T>
void Swap(T &a, T &b)
{
	T c = a;
	a = b;
	b = c;
}

typedef void (FuncI)(int &, int &);
typedef void (FuncD)(double &, double &); // FuncD 是一個類型,而*FuncD是一個函數指針。
typedef void (FuncT)(Test &, Test &);     // 函數參數是類的引用。

int main()
{
	FuncI *pi = Swap;
	// 編譯器自動推導T為int。當編譯到這行時,
	// 發生要用一個模闆去初始化pi(一次編譯!),而pi的類型又是void (FuncI) (int &, int &) ,
	// 是以此時,編譯器會用int去替換T(二次編譯),然後生成一個Swap函數并把指針指派給pi。
	FuncD *pd = Swap;//編譯器自動推導T為double,編譯器生成另一個Swap函數

	// 以下證明pi與pd指向的是兩個不同的函數(這種強制類型轉換用于指針類型,整數和指針類型之間。)
	cout << "pi = " << reinterpret_cast<void *>(pi) << endl;
	cout << "pd = " << reinterpret_cast<void *>(pd) << endl;
	//cout << "pi = " << reinterpret_cast<FuncD *>(pi) << endl;  // 1
	//cout << "pd = " << reinterpret_cast<FuncD *>(pd) << endl;  // 1

	// FuncT *pt = Swap;
	// 編譯器自動推導T為test。
	// 但是當進行T替換時由于Swap函數内部的T c = a;(對象的拷貝)會調用Test的拷貝構造函數,
	// 但被我們故意設定為private,是以編譯出錯,這個例子就是來說明,
	// 第二次編譯的時候會生成另一個不同的版本的Swap函數,參數是類的引用。
	
	// cout << "pt = " << reinterpret_cast<void*>(pt) << endl;

	// 總結:函數模闆會進行兩次編譯,一次是文法類型檢查, 二次是參數替換後進行的編譯。

	system("pause");
	
	return 0;
}
           
  • 運作結果:
    C++深度解析(46)—深入了解函數模闆

2.多參數函數模闆

  • 函數模闆可以定義任意多個不同的類型參數
template <typename T1,typename T2,typename T3>
T1 Add(T2 a,T3 b)
{
    return static_cast<T1>(a + b);
}
​```
int r = Add<int,float,double>(0.5,0.8);
           
  • 對于多參數函數模闆
    • 無法自動推導傳回值類型,必須顯示指定
    • 可以從左向右部分指定類型參數
    • 工程中将傳回值參數類型作為第一個類型參數類型!
// T1 = int, T2 = double, T3 = double
int r1 = Add<int>(0.5, 0.8);

// T1 = double, T2 = float, T3 = double
double r2 = Add<double, float>(0.5, 0.8);

// T1 = float, T2 = float, T3 = float
float r3 = Add<float, float, float>(0.5, 0.8);
           
  • 程式設計實驗:多參數函數模闆
#include <iostream>
#include <string>

using namespace std;

template <typename T1, typename T2, typename T3>  // 聲明三個參數,函數模闆
T1 Add(T2 a, T3 b)
{
	return static_cast<T1>(a + b);
}

int main()
{
	// 置換 T1 = int, T2 = double, T3 = double
	int r1 = Add<int>(0.5, 0.8); // 從左向右指定類型,這裡隻指定傳回值類型,其餘自動推導。

	//T1 = double T2 = float  T3 = double
	double r2 = Add<double, float>(0.5, 0.8); //從左向右指定類型,這裡隻指定傳回值類型和函數裡面的第一個參數,第二個自動推導。

	float r3 = Add<float, float, float>(0.5, 0.8);// 3個類型都顯式指定

	cout << "r1 = " << r1 << endl;
	cout << "r2 = " << r2 << endl;
	cout << "r3 = " << r2 << endl;

	system("pause");
	
	return 0;
}
           
  • 運作結果:
    C++深度解析(46)—深入了解函數模闆

3.函數模闆與重載

  • 函數模闆可以像普通函數一樣被重載
    • C++編譯器優先考慮普通函數
    • 如果函數模闆可以産生一個更好的比對,那麼選擇模闆
    • 可以通過空模闆實參清單限定編譯器隻比對模闆
    • 函數模闆不允許自動類型轉換,普通函數能夠進行自動類型轉換
C++深度解析(46)—深入了解函數模闆
  • 程式設計實驗:重載函數模闆
#include <iostream>
#include <string>

using namespace std;

template <typename T>
T Max(T a, T b)
{
	cout << "T Max(T a, T b)" << endl;
	
	return a > b ? a : b;
}

int Max(int a, int b)
{
	cout << "int Max(int a, int b)" << endl;
	
	return a > b ? a : b;
}

template <typename T>
T Max(T a, T b, T c)
{
	cout << "T Max(T a, T b, T c)" << endl;
	
	return Max(Max(a, b), c);  // 遞歸調用,然後傳回。利用已有的函數實作功能。遞歸的作用代碼複用。
}

int main()
{

	int a = 1;
	int b = 2;

	cout << Max(a, b) << endl; // 優先比對普通函數。

	cout << Max<>(a, b) << endl; // 限定隻能從模闆去比對。

	cout << Max(3.0, 4.0) << endl; // 從模闆去比對,因為函數沒有這個類型

	cout << Max(5.0, 6.0, 7.0) << endl; // 調了兩次模闆,從模闆比對。

	cout << Max('a', 100) << endl;  // 普通函數(普通函數有隐式轉換),函數模闆不允許隐式轉換,是以不會比對
	
	system("pause");
	
	return 0;

}
           
  • 運作結果:
    C++深度解析(46)—深入了解函數模闆

4.小結

  • 函數模闆通過具體類型産生不同的函數
  • 函數模闆可以定義任意多個不同的類型參數
  • 函數模闆中的傳回值類型必須顯示指定
  • 函數模闆可以像普通函數一樣被重載
  • 普通函數有隐式轉換,函數模闆不允許隐式轉換

繼續閱讀