文章目錄
- 1.編程范式
- 2.函數模板
- 2.1 函數模板概念
- 2.2 函數模板原理
- 2.3 函數模板實例化
- 2.3.1 隱式實例化
- 2.3.2 顯式實例化
- 2.4 模板參數的匹配原則
- 3.類模板
- 希望讀者們多多三連支持
- 小編會繼續更新
- 你們的鼓勵就是我前進的動力!
進入STL庫學習之前我們要先了解有關模板的學習,以便在學習完STL庫使用之后,能更深入的了解其底層工作原理
1.編程范式
編程范式
指的是我們使用編程的基本風格和方法
常見的方式有以下幾種:
面向對象編程(OOP)
將數據和操作數據的方法封裝在類中,通過類的實例(對象)來進行交互,強調數據的封裝、繼承和多態性
定義一個Shape
基類,包含計算面積的純虛函數,再派生出Circle
和Rectangle
等類,重寫計算面積的函數,體現了面向對象的繼承
和多態
特性
函數式編程
將計算視為函數的組合和應用,強調不可變數據和純函數,避免副作用,注重函數的輸入輸出關系
使用std::function
和lambda
表達式可以方便地進行函數式編程,如用lambda
表達式定義一個簡單的加法函數
,不修改外部狀態,只返回計算結果
過程式編程
以過程(函數)為中心,將程序分解為一系列的步驟和函數調用,數據和操作數據的函數相對獨立
傳統的C語言
風格的編程方式,如編寫一個計算階乘的函數,通過循環
和遞歸
來實現計算過程,就是典型的過程式編程
泛型編程
定義函數、類或其他程序結構時,不指定具體的數據類型,而是使用類型參數來代表未知的數據類型
在algorithm
頭文件中的swap
函數就是一種常見的泛式編程,他不指定任何類型就能實現交換,依靠的就是泛式編程,也是我們接下來要學習的模板
2.函數模板
在還不知道頭文件前實現swap函數
通常是這樣的:
void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}
void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}
void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}......
為了符合各個場景下實現參數互換,要對同一個函數實現不同類型的函數重載
,這種方式固然可行,但是每個類型都寫一遍太過于冗余了
- 重載的函數僅僅是
類型不同
,代碼復用率比較低
,只要有新類型出現時,就需要用戶自己增加對應的函數 - 代碼的
可維護性比較低
,一個出錯可能所有的重載均出錯
2.1 函數模板概念
我們知道文字的印刷是依靠活字印刷術的模板實現的,那能否告訴編譯器一個模子,讓編譯器根據不同的類型利用該模子來生成代碼呢?
這里用到的模板就是函數模板
,其語法形式為:
template<typename T1, typename T2,......,typename Tn>
template
就是模板的意思,是用來定義模板參數關鍵字,也可以使用class
,切記:不能使用struct
代替class
,因為struct
和class
的默認權限不同,會導致一些混淆和潛在的問題
2.2 函數模板原理
函數模板是一個藍圖,它本身并不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重復的事情交給了編譯器
舉個例子:
template<typename T>
void Swap(T& a, T& b)
{T temp = a;a = b;b = temp;
}
實現一個Swap交換函數
對兩個不同類型的函數進行同一個函數的調用,調試模式下轉到反匯編可以發現,兩個函數式模板示例化后被調用的
這直接說明了調用的不是同一個函數
在編譯器編譯階段
,對于模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數
以供調用。
比如:當用double類型使用函數模板時,編譯器通過對實參類型的推演,將T確定為double類型,然后產生一份專門處理double類型的代碼
,這個類型無論是內置類型
還是自定義類型
都可以
2.3 函數模板實例化
用不同類型的參數使用函數模板時,稱為函數模板的實例化
2.3.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);return 0;
}
正常情況下的調用就是隱式實例化
🔥值得注意的是: Add函數前加const是因為這里如果像下面例子一樣進行強制轉化會生成臨時變量,具有常性
該知識點在前面有提到過:
傳送門:C++命運石之門代碼抉擇:C++入門(中)
2.3.2 顯式實例化
在函數名后的<>中指定模板參數的實際類型
叫作顯式實例化
Add(a1, d1);
還是上面的例子,如果既調用int,又調用double,到底是用哪種類型編譯器無法決定
,就需要顯式實例化
🚩用戶自己來強制轉化
Add(a1, (int)d1);
🚩使用顯式實例化
Add<int>(a1, d1);
指定T的類型為int
這通常不是顯式實例化的常用場景,舉個例子:
template<class T>
T* Alloc(int n)
{return new T[n];
}int main()
{Alloc<int>(5);return 0;
}
如果寫成Alloc(5)
,編譯器不知道你要分配的是int數組
、double數組
還是其他類型的數組,所以無法自動推導T的類型
,這時候就需要顯式指定模板參數,像Alloc<int>(5)
這樣明確告訴編譯器T是int類型
2.4 模板參數的匹配原則
🚩一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化為這個非模板函數
// 專門處理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版本
}
🚩對于非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數, 那么將選擇模板
// 專門處理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函數
}
🚩模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換
這里的自動轉化就是上面的實例化中的轉化,也要和auto自動推導區分開,不是同一個東西
3.類模板
類模板其實和函數模板是類似的
其語法形式為:
template<class T1, class T2, ..., class Tn>
因為類不像函數那樣語法上支持自動類型轉化,所以類模板調用必須顯式實例化
// 動態順序表
// 注意: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;
}int main()
{// Vector類名,Vector<int>才是類型Vector<int> s1;Vector<double> s2;return 0;
}
我們在寫模板類時盡量不要聲明定義分離
,原因有些復雜放在模板進階的時候講,如果一定分離的話要注意:
- 對于
普通類
,類名和類型一樣 - 對于
模板類
,Vector類名
,Vector<int>才是類型