1. 泛型編程
void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}void Swap(double& x, double& y)
{double tmp = x;x = y;y = tmp;
}void Swap(char& x, char& y)
{char tmp = x;x = y;y = tmp;
}
int main()
{int a = 10, b = 20;double c = 1.1, d = 2.2;char m = '#', n = '*';Swap(a, b);Swap(c, d);Swap(m, n);return 0;
}
我們看這段代碼,使用c++的函數重載技術實現了不同類型的交換。但這段代碼未免太過冗雜和繁瑣,我們寫了很多近似重復的東西。
1. 重載的函數僅僅是類型不同,代碼復用率比較低,只要有新類型出現時,就需要用戶自己增加對應的函數。
2. 代碼的可維護性比較低,一個出錯可能所有的重載均出錯。
那么我們能不能寫一個模具,讓各個類型的變量都能復用它呢?這就是我們這節要學的泛型編程之模板。就像下面這樣,給出一個模具,可以做出各種模型。
如果在C++中,也能夠存在這樣一個模具,通過給這個模具中填充不同材料(類型),來獲得不同材料的鑄件(即生成具體類型的代碼),那將會節省許多精力。
泛型編程:編寫與類型無關的通用代碼,是代碼復用的一種手段。模板是泛型編程的基礎。
模板分為函數模板與類模板。
?
2. 函數模板?
2.1 概念
函數模板代表了一個函數家族,該函數模板與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。
2.2 格式
?
template<typename T1, typename T2,......,typename Tn>
返回值類型 函數名(參數列表){}
譬如我們將上面的Swap函數模板化。
template<class T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}int main()
{int a = 10, b = 20;double c = 1.1, d = 2.2;char m = '#', n = '*';Swap(a, b);Swap(c, d);Swap(m, n);cout << a << " " << b << endl;cout << c << " " << d << endl;cout << m << " " << n << endl;return 0;
}
?
注意:typename是用來定義模板參數關鍵字,也可以使用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);/*該語句不能通過編譯,因為在編譯期間,當編譯器看到該實例化時,需要推演其實參類型通過實參a1將T推演為int,通過實參d1將T推演為double類型,但模板參數列表中只有一個T,編譯器無法確定此處到底該將T確定為int 或者 double類型而報錯注意:在模板中,編譯器一般不會進行類型轉換操作,因為一旦轉化出問題,編譯器就需要背黑鍋Add(a1, d1);*/// 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化Add(a, (int)d);return0;
}
2. 顯式實例化:在函數名后的<>中指定模板參數的實際類型?
int main(void)
{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函
數
}
定義了一個Add函數的模板,有兩個模板參數類型,對于模板函數而言恰好,但對于非函數模板類型而言,需要隱式類型轉換,明顯模板函數更加匹配。?
3. 模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換
?
3. 類模板
3.1 類模板定義格式?
template<class T1, class T2, ..., class Tn>
class 類模板名
{
// 類內成員定義
};
// 動態順序表
// 注意:Vector不是具體的類,是編譯器根據被實例化的類型生成具體類的模具
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);
void PopBack();
// ...
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;_size = _capacity = 0;
}
3.2 類模板的實例化
類模板實例化與函數模板實例化不同,類模板實例化需要在類模板名字后跟<>,然后將實例化的類型放在<>中即可,類模板名字不是真正的類,而實例化的結果才是真正的類。?
// Vector類名,Vector<int>才是類型
Vector<int> s1;
Vector<double> s2;
范例
#include<iostream>
using namespace std;
template<class T>
class Stack
{
public:Stack():_a(new T[10]), _size(0), _capacity(10){}void push(const T& x);void STPop();T STTop();~Stack();
private:T* _a;int _capacity;int _size;
};template<class T>
Stack<T>::~Stack()
{delete[] _a;_size = 0;_capacity = 0;
}
template<class T>
void Stack<T>::push(const T& x)
{_a[_size++] = x;
}
template<class T>
void Stack<T>::STPop()
{_size--;}
template<class T>
T Stack<T>::STTop()
{return _a[_size - 1];
}
int main()
{Stack<int> s1;s1.push(1);s1.push(2);s1.push(3);cout << s1.STTop() << endl;s1.STPop();cout << s1.STTop() << endl;s1.STPop();cout << s1.STTop() << endl;s1.STPop();return 0;
}