C+± 泛型程式設計之函數模闆(詳解)
本章學習:
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
轉:25.C+± 泛型程式設計之函數模闆(詳解)