C++模板:模板简述,函数模板详细说明【C++模板】(56)
- 模板
- 模板語義
- 函數模板
- 重載泛化
- 函數模板
- 語法
- 模板泛化
- 特性小結
- 編譯原理
- 函數模板應用
- 算法抽象
- 快速排序算法實現模板化
- 函數模板默認參數
- 函數模板的特化
- 函數模板適用場景
模板
模板語義
模板是一門語言,支持泛型的語法基礎。
泛型(Generic Programming),是指在多種數據類型上皆可操作的含意。泛型編程的代表作品 STL 是一種高效、泛型、可交互操作的軟件組件。
泛型編程最初誕生于 C++中,目的是為了實現 C++的 STL(標準模板庫)。其語言支持機制就是模板(Templates)。
模板的精神其實很簡單:類型參數化(type parameterized),即類型也是一種參數,也是一種靜多態。
換句話說,把一個原本特定于某個類型的算法或類當中的類型信息抽掉,抽象出來做成模板參數 T。
函數模板
重載泛化
重載函數,雖然實現了泛化的一些特性,但是不徹底,且有二義性(ambiguous)的存在。
代碼演示:
#include <iostream> using namespace std;void myswap(int & a, int & b) {int t = a;a = b;b = t; }void myswap(double & a, double & b) {double t = a;a = b;b = t; } int main() {double a = 2; double b = 3;myswap(a, b);cout << a << " " << b << endl;int aa = 2; int bb = 3;myswap(aa, bb);cout << aa<<" " << bb << endl;return 0; }運行結果為:
如下,示例中 long 類型可以隱式的轉化為 int 或是 double,編譯會出現二義性,導致編譯失敗。
代碼演示:
編譯器報錯:
可以增加函數重載類型來解決上面的問題,也是一種最好的解決方案。
例如增加:
void myswap(long & a, long & b) //函數重載 {long t = a;a = b;b = t; }也可以使用模板來解決函數重載的泛化不徹底的問題。
使用模板實現徹底泛化。
函數模板
語法
//在一個函數的參數表,返回類型和函數體中使用參數化的類型。 template<typename 類型參數 T1,typename/class 類型參數 T2,...> 返回類型 函數模板名(函數參數列表) {函數模板定義體 }模板泛化
template,既可以與函數同行,也可以將函數另起一行來書寫。T 即為范化的類型。
其過程,相當于經歷了兩次編譯,先依據實參類型,實例化函數,然后再將實例化的函數,參與編譯。
代碼演示:
#include <iostream> using namespace std;template<typename T> void myswap(T & a, T & b) {T t = a;a = b;b = t; }int main() {long a = 2; long b = 3;myswap(a, b);cout << a << " " << b << endl;return 0; }運行結果:
我們說重載對于泛化不徹底,模板徹底實現泛化,我們接下來進行說明:
代碼演示:
運行結果:
那么就會引發我們思考:
模板是不是悄悄的把我們學習過的所有類型全部重載了一遍呢?
我們通過代碼進行驗證:自實現類型使用模板。
代碼演示:
運行結果:
我們可以看到,不只是系統提供的數據類型可以實現模板泛化,自定義的類也可以實現模板泛化。
所以模板解決了所有類型的操作,不管是基礎類型還是自定義類型。
模板的原理:
其實模板的使用本來應該是如下的使用方式:
代碼演示:
運行結果:
只不過現在類型推導足夠強大了,所以
myswap<long>(a, b); myswap<string>(s1,s2); myswap<double>(d1, d2); myswap<MyType>(mt1, mt2);就省略成了:
myswap(a, b); myswap(s1,s2); myswap(d1, d2); myswap(mt1, mt2);上面代碼的過程,相當于經歷了兩次編譯,先依據實參類型,實例化函數。然后再將實例化的函數,參與編譯。
函數模板myswap
–>實例化為
模板函數myswap < int >
–>模板函數的調用myswap < int >( 1 , 2 )
一般來說模板經歷兩個過程:
編譯器編譯檢查模板的語法是否正確,語法通過時。當發生某一具體調用時,根據特定類型(類型由我們明確的告訴編譯器或者進行自動類型推導)然后產生特定類型的模板函數版本用來調用。
什么叫做類型參數化呢?
在模板中可以設置默認類型:
代碼演示:
一般情況下不給默認值。
myswap<long>(a, b);也不寫類型。
因為類型推導已經強大到不需要寫。
那么也就是把類型當作參數傳遞給函數模板,然后函數模板實例化為模板函數進行調用。
特性小結
先實例化,再調用。
嚴格匹配,不存在隱式轉化。
尺有所短,寸有所長。
編譯原理
編譯器遇到模板方法定義時,會進行語法檢查,但是并不編譯模板。編譯器無法編譯模板定義,因為它不知道使用什么類型。比如 T a,b; 在不知曉 T 具體類型時,是無法分配內存,更無從談編譯 a = b;
T 獲取類型的過程,稱為模板的實例化,函數模板通過具體類型產生不同的函數;編譯器會對函數模板進行兩次編譯:在聲明的地方對模板代碼本身進行編譯(類型檢查),在調用的地方對參數替換后的代碼進行編譯(代碼生成)。
那么模板既然這么好,模板是不是就沒有缺陷了呢?
編譯器并不是把函數模板處理成能夠處理任意類的函數。比如,自定義類型,如何處理呢?
代碼演示:
#include <iostream> using namespace std;class MyType { public:MyType(int x,int y):_x(x), _y(y){}int _x;int _y; };template<typename T = int> void myswap(T & a, T & b) {T t = a;a = b;b = t; }int main() {int i = 3;double j = 1.2;myswap(i, j);return 0; }編譯器報錯:
那么我們可以不可以傳遞兩個類型來實現模板呢?
代碼演示:
結論是不可以,詳細原因在代碼中已經給出。
模板參數做返回類型需要編譯器支持類型推導,需要確定返回類型來解決。
函數模板應用
算法抽象
數據類型的泛化,使的模板特別適合于作算法的抽象。
STL 中的 algorithm 就是典型的代表。
比如,算法 sort 就是支持類型泛化的代表排序算法。
例如:我把東西從A地搬到B地我只考慮怎么搬更省力而不考慮搬的是什么東西。
快速排序算法實現模板化
將快速排序算法實現模板化。此時,無論轉入的是 int 的還是 float 的類型,均是可以處理。相同邏輯的函數沒有必要寫兩遍。
代碼演示:
#include <iostream> #include <typeinfo> using namespace std; template<typename T> void quickSort(T* array, int left, int right) {if (left < right){int low = left; int high = right;T pivot = array[low];while (low < high){while (array[high] >= pivot && high > low)high--;array[low] = array[high];while (array[low] <= pivot && high > low)low++;array[high] = array[low];}array[low] = pivot;quickSort(array, left, low - 1);quickSort(array, low + 1, right);} }int main() {int array[10] = { 1,3,5,7,2,4,6,8,0,9 };quickSort(array, 0, 9);for (auto i : array){cout << i << endl;} }運行結果:
那么我們對于數據類型進行修改:
代碼演示:
運行結果為:
也可以對于字符串進行排序:
對于main函數進行修改:
運行結果:
寫模板時,先用基本類型實現,然后套用模板,然后進行測試。
函數模板默認參數
重在理解,類型參數化,此時的默認參數,不再是一數值,而是類型。
函數模板,在調用時,先實例化為模板函數,然后再調用。當然也可以設置默認類型的默認值。由于系統強大的自動推導能力,有時默認也沒有太大的意義。
template<typename T = int> void quickSort(T * array,int left, int right)函數模板的特化
Template Specialization,函數模板的特化,即函數模板的特殊情況,個例行為。
就是在實例化模板時,對特定類型的實參進行特殊處理,即實例化一個特殊的實例版本。
template<typename T> int compare( T &a,T &b) template<> int compare <const char*>( const char*&a, const char*&b)當以特化定義時的形參使用模板時,將調用特化版本,模板特化分為全特化和偏特化,函數模板的特化,只能全特化;
比如,我們在比較兩個數的大小時:
代碼演示:
運行結果:
上面的代碼是模板的正常使用,沒有問題。
我們對于代碼進行修改:
#include <iostream> #include <string.h> using namespace std;template<typename T> int compare(T& a,T& b) {if (a > b) return 1;else if (a < b)return -1;else return 0; }int main() {string sa = "abcd";string sb = "abc";cout << compare(sa, sb) << endl;const char * ca = "abcd";const char * cb = "abc";cout << compare(ca, cb) << endl;return 0; }運行結果:
我們可以看到上面的測試運行出來了不同的結果。
原因分析:
實參為兩個 char * 時,比較的是指針地址的大小,而不是指針指向內容的大小。
此時就需要為該函數模板定義一個特化版本,即特殊處理的版本:
代碼演示:
#include <iostream> #include <string.h> using namespace std;template<typename T> int compare(T& a, T& b) {if (a > b) return 1;else if (a < b)return -1;else return 0; }//模板特化版本 template<> int compare < const char* >(const char*& a, const char*& b) {return strcmp(a, b); }int main() {const char * ca = "abcd";const char * cb = "abc";cout << compare(ca, cb) << endl;return 0; }運行結果:
函數模板適用場景
函數模板,只適用于函數的參數個數相同而類型不同,且函數體相同的情況。
總結
以上是生活随笔為你收集整理的C++模板:模板简述,函数模板详细说明【C++模板】(56)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++IO流,istream statu
- 下一篇: C++模板:类模板和类模板的友元【C++