【C ++基礎】第9篇 模闆初階(泛型程式設計|函數模闆|類模闆)
前言
本文所屬專欄:C++學習_潮.eth的部落格-CSDN部落格
參考教材:《C++ Prmer》第五版 P578
目錄
文章目錄
- 【C ++基礎】第9篇 模闆初階(泛型程式設計|函數模闆|類模闆)
-
- 一、泛型程式設計
-
- 1.泛型程式設計是什麼
- 2.實作一個交換函數(函數重載版)
- 3.模闆概念
- 二、函數模闆
-
- 1.函數模闆的概念
- 2.函數模闆的格式
- 4.函數模闆的執行個體化
-
- 4.1 隐式執行個體化
- 4.2 顯示執行個體化
- 5. 模闆參數的比對原則
- 三、類模闆
-
- 1.類模闆的定義格式
- 2.類模闆的執行個體化
正文
一、泛型程式設計
1.泛型程式設計是什麼
泛型程式設計:編寫程式 與類型無關 的通用代碼,是代碼複用的一種手段。模闆是泛型程式設計的基礎。
2.實作一個交換函數(函數重載版)
假定我們希望編寫一個函數來交換兩個變量的值。在實際中,我們可能想要定義多個函數,每個函數比較一種給定類型的值。我們的初次嘗試可能定義多個重載函數:
//int
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
//double
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
這兩個函數幾乎是相同的,唯一的差異是參數的類型,函數體則幾乎一樣。
使用函數重載雖然可以實作,但是有一下幾個不好的地方:
- 重載的函數僅僅是類型不同,代碼複用率比較低 。隻要有新類型出現時,就需要使用者自己增加對應的函
數,不妨假想一下,萬一每個類型都要出現,那工作量大大增加。
- 代碼的可維護性比較低 ,一個出錯可能所有的重載均出錯。
3.模闆概念
對于交換函數(函數重載版)的缺點是需要寫太多僅類型不同的重載函數,那麼能不能告訴編譯器一個模子,讓編譯器根據不同的類型利用該模子來生成代碼呢?
(啰嗦版)如果在C++中,也能夠存在這樣的一個模具,通過給這個模具中填充不同材料(就是我們需要的類型),來獲得不同材料的鑄件(即生成具體類型的代碼),那将會節省很多麻煩。
(簡潔版)我們想要什麼類型,編譯器就生成什麼類型的代碼!
C++給我們提供了一種這樣的解決方案,那就是 模闆。
一個模闆就是一個建立類或函數的藍圖或者說是公式!
二、函數模闆
1.函數模闆的概念
函數模闆代表了一個函數家族,該函數模闆與類型無關,在使用時被參數化,根據實參類型産生函數的特定類型版本。
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(切記不能使用struct代替class)
4.函數模闆的執行個體化
函數模闆的執行個體化:用不同類型的參數使用函數模闆
模闆參數執行個體化分為:隐式執行個體化和顯示執行個體化。
4.1 隐式執行個體化
隐式執行個體化:讓 編譯器 根據實參 推演 模闆參數的 實際類型。
一個Add()函數,實作兩個數相加的功能
template<class T>
T Add(const T& left,const T& right)
{
return left + right;
}
然後嘗試使用不同類型,這裡使用 int 跟 double
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
return 0;
}
那麼不同類型能不能加:
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, d1);
return 0;
}
當然不能啦,看一下原因:
Add ( a1 , d1 ) 這條語句不能通過編譯,因為在編譯期間,當編譯器看到該執行個體化時,需要推演其 實參類型
通過實參 a1 将 T 推演為 int ,通過實參 d1 将 d1 将 T 推演為 double 類型,但模闆參數清單隻有一個 T ,
編譯器無法确定此處到低該不該将 T 确定為 int 或者 double 類型而報錯
注意:在模闆中,編譯器一般不會進行類型的轉換操作,因為一旦轉換出問題,編譯器就需要背黑鍋
那麼如何解決呢?
一種方法是使用者自己來 強制轉化
就像下面這樣:
還有一種方法是使用 顯示執行個體化
4.2 顯示執行個體化
顯示執行個體化 寫法:在函數名後的<>中指定模闆參數的實際類型
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
//顯示執行個體化
Add<int>(a, b);
return 0;
}
如果類型不比對,編譯器會嘗試進行隐式類型轉換,如果無法轉換成功,編譯器會警告!
5. 模闆參數的比對原則
原則一:
一個非模闆函數可以和一個同名的模闆同時存在,而且該函數模闆還可以被執行個體化為這個非模闆函數
//專門處理int 的加法函數
int Add(int left, int right)
{
return left + right;
}
//通用加法函數
template<class T>
T Add(T left,T right)
{
return left + right;
}
int main()
{
Add(1, 2); //與非模闆函數比對,編譯器不需要特化
Add<int>(1, 2); //調用編譯器特化的Add版本
return 0;
}
原則二:
對于非模闆函數和同名函數模闆,如果其他條件都相同,在調用時會優先調用非模闆函數而不會從該模闆産生出一個執行個體。如果模闆可以産生一個具有更好比對的函數,那麼将選擇模闆
(簡化版)更加比對(不需要轉換的那種)就用模闆。
//專門處理int 的加法函數
int Add(int left, int right)
{
return left + right;
}
//通用加法函數
template<class T1,class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
int main()
{
//與非函數模闆類型完全比對
//不需要函數模闆執行個體化
Add(1, 2);
//模闆函數可以生成更加比對的版本
//編譯器根據實參生成更加比對的Add函數
Add<int>(1, 2.0);
return 0;
}
原則三:
模闆函數不允許自動類型轉換,但普通函數可以進行自動類型轉換
三、類模闆
1.類模闆的定義格式
template<class T1,class T2,...,class Tn>
class 類模闆名
{
//類成員定義
};
2.類模闆的執行個體化
類模闆執行個體化與函數模闆執行個體化不同,類模闆執行個體化需要在類模闆名字後跟<>,然後将執行個體化的類型放在<>即可,類模闆名字不是真正的類,而執行個體化的結果才是真正的類
//Vector類名,Vector<int>才是類型
Vector<int> s1;
Vector<double> s2;
全文完。