本章學習:
1)初探函數模板
2)深入理解函數模板
3)多參函數模板
4)重載函數模板
當我們想寫個Swap()交換函數時,通常這樣寫:
void Swap(int& a, int& b) { int c = a; a = b; b = c; }
但是這個函數僅僅只能支持int類型,如果我們想實現交換double,float,string等等時,就還需要從新去構造Swap()重載函數,這樣不但重復勞動,容易出錯,而且還帶來很大的維護和調試工作量。更糟的是,還會增加可執行文件的大小.
所以C++引入了泛型編程概念
在C++里,通過函數模板和類模板來實現泛型編程(類模板在下章將講解)
函數模板
- 一種特殊的函數,可通過不同類型進行調用
- 函數模板是C++中重要的代碼復用方式
- 通過template關鍵字來聲明使用模板
- 通過typename關鍵字來定義模板類型
比如:
template <typename T> //聲明使用模板,並定義T是一個模板類型 void Swap(T& a, T& b) //緊接着使用T { T c = a; a = b; b = c; }
當我們使用int類型參數來調用上面的Swap()時,則T就會自動轉換為int類型.
函數模板的使用
- 分為自動調用和顯示調用
例如,我們寫了一個Swap函數模板,然后在main()函數里寫入:
int a=0; int b=1; Swap(a,b); //自動調用,編譯器根據a和b的類型來推導 float c=0; float d=1; Swap<float>(c,d); //顯示調用,告訴編譯器,調用的參數是float類型
初探函數模板
寫兩個函數模板,一個用來排序數組,一個用來打印數組,代碼如下:
#include <iostream> #include <string> using namespace std; template < typename T > void Sort(T a[], int len) { for(int i=1;i<len;i++) for(int j=0;j<i;j++) if(a[i]<a[j]) { T t=a[i]; a[i]=a[j]; a[j]=t; } } template < typename T > void Println(T a[], int len) { for(int i=0; i<len; i++) { cout << a[i] << ", "; } cout << endl; } int main() { int a[5] = {5, 3, 2, 4, 1}; Sort(a, 5); //自動調用,編譯器根據a和5的類型來推導 Println<int>(a, 5); //顯示調用,告訴編譯器,調用的參數是int類型 string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"}; Sort(s, 5); Println(s, 5);
return 0; }
運行打印:
1,2,3,4,5, Basic,C++, Java,Pascal,Ruby,
深入理解函數模板
為什么函數模板能夠執行不同的類型參數?
答:
- 其實編譯器對函數模板進行了兩次編譯
- 第一次編譯時,首先去檢查函數模板本身有沒有語法錯誤
- 第二次編譯時,會去找調用函數模板的代碼,然后通過代碼的真正參數,來生成真正的函數。
- 所以函數模板,其實只是一個模具,當我們調用它時,編譯器就會給我們生成真正的函數.
試驗函數模板是否生成真正的函數
通過兩個不同類型的函數指針指向函數模板,然后打印指針地址是否一致,代碼如下:
#include <iostream> using namespace std;
template <typename T> void Swap(T& a, T& b) { T c = a; a = b; b = c; } int main() { void (*FPii)(int&,int&); FPii = Swap ; //函數指針FPii void (*FPff)(float&,float&); FPff = Swap ; //函數指針FPff cout<<reinterpret_cast<void *>(FPii)<<endl; cout<<reinterpret_cast<void *>(FPff)<<endl; //cout<<reinterpret_cast<void *>(Swap)<<endl; //編譯該行會出錯,因為Swap()只是個模板,並不是一個真正函數 return 0; }
運行打印:
0x41ba98 0x41ba70
可以發現兩個不同類型的函數指針,指向同一個函數模板,打印的地址卻都不一樣,顯然編譯器默默幫我們生成了兩個不同的真正函數
多參數函數模板
在我們之前小節學的函數模板都是單參數的, 其實函數模板可以定義任意多個不同的類型參數,例如:
template <typename T1,typename T2,typename T3> T1 Add(T2 a,T3 b) { return static_cast<T1>(a+b); }
注意:
- 工程中一般都將返回值參數作為第一個模板類型
- 如果返回值參數作為了模板類型,則必須需要指定返回值模板類型.因為編譯器無法推導出返回值類型
- 可以從左向右部分指定類型參數
接下來開始試驗多參數函數模板
#include <iostream> using namespace std; template<typename T1,typename T2,typename T3> T1 Add(T2 a,T3 b) { return static_cast<T1>(a+b); } int main() { // int a = add(1,1.5); //該行編譯出錯,沒有指定返回值類型 int a = Add<int>(1,1.5); cout<<a<<endl; //2 float b = Add<float,int,float>(1,1.5); cout<<b<<endl; //2.5 return 0; }
運行打印:
2 2.5
重載函數模板
- 函數模板可以像普通函數一樣被重載
- 函數模板不接受隱式轉換
- 當有函數模板,以及普通重載函數時,編譯器會優先考慮普通函數
- 如果普通函數的參數無法匹配,編譯器會嘗試進行隱式轉換,若轉換成功,便調用普通函數
- 若轉換失敗,編譯器便調用函數模板
- 可以通過空模板實參列表來限定編譯器只匹配函數模板
接下來開始試驗重載函數模板
#include <iostream> 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; } 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; } int main() { int a=0; int b=1; cout<<"a:b="<<Max(a,b) <<endl ; //調用普通函數 Max(int,int) cout<<"a:b="<<Max<>(a,b)<<endl; //通過模板參數表 調用 函數模板 Max(int,int) cout<<"1.5:2.0="<<Max(1.5,2.0)<<endl; //由於兩個參數默認都是double,所以無法隱式轉換,則調用函數模板 Max(double,double) int *p1 = new int(1); int *p2 = new int(2); cout<<"*p1:*p2="<<Max(p1,p2)<<endl; // 調用重載函數模板 Max(int* ,int* ) cout<<"'a',100="<< Max('a',100)<<endl; //將char類型進行隱式轉換,從而調用普通函數 Max(int,int) delete p1; delete p2; return 0; }
運行打印:
int Max(int a,int b) a:b=1
T Max(T a,T b) a:b=1
T Max(T a,T b) 1.5:2.0=2
T Max(T* a,T* b) *p1:*p2=2
int Max(int a,int b) 'a',100=100
接下來下章來學習: 26.C++- 泛型編程之類模板(詳解)