1. 程式人生 > >c++的函式模板

c++的函式模板

c++的函式模板

首先為什麼要引進函式模板?我們來看一個案例:

我們在這兒想要實現一個通用的交換資料的函式
void Swap(int left, int right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(char left, char right)
{
	char temp = left;
	left = right;
	right = temp;
}
void Swap(double left, double right)
{
	double temp = left;
	left = right;
	right = temp;
}
  • 以上的程式碼我使用了函式過載的方式,為了要儘可能多的來滿足所有型別的元素,但是很明顯,是不行的。因為型別實在太多了,如果使用函式過載的話,恐怕有點難為情。
  • 所以c++就引入了函式模板
  • 函式模板的格式
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(切記:不能使用struct代替class)

  • 現在我來介紹一下模板函式的用法。
  • 我們看到 template< typename T>這句話,其中template 是模板的意思,typename 是型別名,而T呢,只能算是一個引數吧,可以換成其他的也可以,為什麼要用T,只是因為大家都用T用習慣了,所以你隨便用。同時也支援模板中多種型別,例如:template< class T1, class T2>;兩個引數可以,三個也行,沒有上限。
  • 讓我們再來理解一下,上面的三個過載函式中,除了型別int/double/char 之外,別的程式碼都是相同的,那麼用T 把int/double/char替換掉就是模板函數了。那麼你可以簡單的把T 認為是一個型別。
  • 再讓我們看看程式碼
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);
	/*
	該語句不能通過編譯,因為在編譯期間,當編譯器看到該例項化時,需要推演其實參型別
	通過實參a1將T推演為int,通過實參d1將T推演為double型別,但模板引數列表中只有一個T,
	編譯器無法確定此處到底該將T確定為int 或者 double型別而報錯
	注意:在模板中,編譯器一般不會進行型別轉換操作,因為一旦轉化出問題,編譯器就需要背黑鍋
	Add(a1, d1);
	*/
	// 此時有兩種處理方式:1. 使用者自己來強制轉化 2. 使用顯式例項化
	Add(a, (int)d);
	return 0;
}
  • 上面的main函式中Add(a1,a2);呼叫的時候,函式模板就會建立一個int 型別的相加函式,就是把函式模板中的T 替換成int 型別。此時就會創建出一個int型別相加的函式。
  • 那麼我們說函式模板並不是函式,只是一個模板,只有在例項化的時候,編譯器才會建立相應型別的函式。
int main(void)
{
	int a = 10;
	double b = 20.0;
	// 顯式例項化
	Add<int>(a, b);
	return 0;
}
  • 上面的Add< int>(a, b) ,為什麼要加上< int>呢?就是為了要函式模板成為int型別的函式,不管我的a和b是什麼型別,都會轉換成int型別的。
// 專門處理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版本
}
  • 上面的程式碼中既有模板函式,又有一般的int型別的函式,那麼在如果Add(1,2); 那麼首先選擇與非模板函式去匹配,因為在編譯階段int型別的函式已經存在,而模板函式必須要在例項化的時候才去建立。所以當找不到相同型別的一般函式才回去匹配模板函式,如果模板函式也匹配不上,那就報錯了。
  • 但是如果顯式的宣告模板函式,那麼編譯器就不會去呼叫一般函式,例如:Add< int>(1,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函式
}
  • 上面的程式碼主要看一下Add(1, 2.0); 首先套路,先看一般函式能不能匹配上,不能,要呼叫話只能強制型別轉換,編譯器先不會這樣做,先看模板函式能不能匹配上,如果可以匹配上就不會強制型別轉換,如果模板函式匹配不上,就會強制型別轉換去呼叫一般函式。
  • 最重要的一點:模板函式不允許型別轉換,是啥就是啥,比較執著。但是一般函式就比較隨意,編譯器可以根據情況進行型別轉換。

接下來再來說說類模板

template<class T1, class T2, ..., class Tn>
class 類模板名
{
	// 類內成員定義
};
  • 再來看看類模板程式碼
#include<iostream>
#include<assert.h>
using namespace std;

template<class T>
class Vector
{
public:
	Vector(size_t capacity = 10)
		: _pData(new T[capacity])
		, _size(0)
		, _capacity(capacity)
	{}
	// 使用解構函式演示:在類中宣告,在類外定義。
	~Vector();
	void PushBack(const T& data)
	{
		// _CheckCapacity();
		_pData[_size++] = data;
	}
	void PopBack()
	{
		--_size;
	}
	size_t Size()
	{
		return _size;
	}
	T& operator[](size_t pos)
	{
		assert(pos < _size);
		return _pData[pos];
	}
private:
	T* _pData;
	size_t _size;
	size_t _capacity;
};

template <class T>
Vector<T>::~Vector()
{
	if (_pData)
	{
		delete[] _pData;
	}
}

int main()
{
	Vector<int> s1;
	s1.PushBack(1);
	s1.PushBack(2);
	s1.PushBack(3);
	Vector<double> s2;
	s2.PushBack(1.0);
	s2.PushBack(2.0);
	s2.PushBack(3.0);
	for (size_t i = 0; i < s1.Size(); ++i)
	{
		cout << s1[i] << " ";
	}
	cout << endl;
	for (size_t i = 0; i < s2.Size(); ++i)
	{
		cout << s2[i] << " ";
	}
	cout << endl;
	return 0;
}
  • 上面的程式碼就是類模板的一些方法。

注意一點:類模板不能分檔案來寫,否則會出現問題。詳情參考部落格: http://blog.csdn.net/pongba/article/details/19130