函數模板與類模板?看這就夠了

C++函數模板與類模板

1. 泛型編程

如何實現一個通用的交換函數呢?
請看下列代碼

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}

void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

雖然使用函數重載雖然可以實現,但是有兩個不好的地方:

1. 重載的函數僅僅只是類型不同,代碼冗餘嚴重。要是有新類型出現時,就必須增加對應的函數。
2. 代碼的可維護性比較低,一個出錯可能所有的重載均會出錯。

那能否給編譯器一個“模具”,通過給這個“模具”中填充不同材料(類型),來獲得不同材料的鑄件(生成具體類型的代碼)呢?

在C++中我們可以使用泛型編程來解決。

泛型編程:編寫與類型無關的通用代碼,是代碼複用的一種手段。模板是泛型編程的基礎。

模板分類

2. 函數模板

2.1. 什麼是函數模板

函數模板代表了一個函數家族,該函數模板與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。

2.2. 函數模板格式

template<typename T1, typename T2,…,typename Tn>
返回值類型 函數名(參數列表){}

template<typename T>
void Swap( T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
} 

注意:typename是用來定義模板參數關鍵字,也可以使用class(一般使用class)
切記:不能使用struct代替class

2.3. 函數模板的原理

函數模板是一個藍圖,它本身並不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重複的事情交給了編譯。

模板的推演

在編譯器編譯階段,對於模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。

比如:當用double類型使用函數模板時,編譯器通過對實參類型的推演,將T確定爲double類型,然後產生一份專門處理double類型的代碼,對於字符類型也是如此。

2.4. 函數模板的實例化

用不同類型的參數使用函數模板時,稱爲函數模板的實例化。模板參數實例化分爲:隱式實例化和顯式實例化

1. 隱式實例化:讓編譯器根據實參推演模板參數的實際類

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2);
	Add(d1, d2);
	/*
	Add(a1, d1);
	該語句不能通過編譯,因爲在編譯期間,當編譯器遇到該實例化時,需要推演其實參類型
	通過實參a1將T推演爲int,通過實參d1將T推演爲double類型,但模板參數列表中只有一個T,
	編譯器無法確定此處到底該將T確定爲int 或者double類型,所以報錯
	注意:在模板中,編譯器一般不會進行類型轉換操作
	*/
	// 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化
	Add(a, (int)d);
	return 0;
}

2. 顯式實例化:在函數名後的<>中指定模板參數的實際類型

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

int main()
{
	int a = 10;
	double b = 20.0;
	// 顯式實例化
	Add<int>(a, b);
	return 0;
}
2.5. 模板參數的匹配原則

1. 一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化爲這個非模板函數。

// 專門處理int的加法函數
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函數
template<class T>
T Add(T left, T right)
{
	return left + right;
}

void test()
{
	Add(1, 2); // 與非模板函數匹配,編譯器不需要特化
	Add<int>(1, 2); // 調用編譯器特化的Add版本
}

2. 對於非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數, 那麼將選擇模板。

// 專門處理int的加法函數
int Add(int left, int right)
{
	return left + right;
}

// 通用加法函數
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); 
	Add(1, 2.0); // 與非函數模板類型完全匹配,不需要函數模板實例化
	// 模板函數可以生成更加匹配的版本,編譯器根據實參生成更加匹配的Add函數 
} 

3. 模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換

3. 類模板

3.1. 類模板的定義格式
template<class T1, class T2, ..., class Tn>
class 類模板名
{
	// 類內成員定義
};

:請看下面類模板的代碼(動態順序表)

// 動態順序表
// 注意:seqlist不是具體的類,是編譯器根據被實例化的類型生成具體類的模具
namespace N
{
	// typedef int SLDadaType
	template <class T>
	class seqlist
	{
	public:
		seqlist(size_t n = 10)
			:_a(new T[n])
			, _size(0)
			, _capacity(n)
		{}

		~seqlist()
		{
			if (_a != nullptr)
			{
				delete[] _a;
				_size = 0;
				_capacity = 0;
			}
		}

		void PushBack(T x)
		{
			// 尾插操作
		}

		T& operator[](size_t i)
		{
			assert(i < _size);
			return _a[i];
		}

	private:
		T*	   _a;
		size_t _size;
		size_t _capacity;
	};
}

int main()
{
	N::seqlist<int> seqcpp1; // 將順序表中的數據轉化爲int
	seqcpp1.PushBack(1);
	seqcpp1.PushBack(2);
	seqcpp1.PushBack(3);
	cout << seqcpp1[0] << endl; // 讀第i個數據的值

	seqcpp1[0] = 0; // 修改第i個數據的值

	N::seqlist<double> seqcpp2; // 將順序表中的數據轉化爲double
	seqcpp2.PushBack(1);
	seqcpp2.PushBack(2);
	seqcpp2.PushBack(3);
	return 0;
}
3.2. 類模板的實例化

類模板實例化與函數模板實例化不同,類模板實例化需要在類模板名字後跟<>,然後將實例化的類型放在<>中即可,類模板名字不是真正的類,而實例化的結果纔是真正的類。

// Vector類名,Vector<int>纔是類型
Vector<int> s1;
Vector<double> s2; 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章