😎【博客主頁:你最愛的小傻瓜】😎
🤔【本文內容:C++模板?😍? ? ? 】🤔
---------------------------------------------------------------------------------------------------------------------------------
C++ 模板是代碼世界里的「萬能詩卷」—— 它以一行行凝練的語法,寫下不被類型束縛的韻律。
當你為整數鋪陳邏輯,它便化作丈量數字的標尺;當你要為字符編織算法,它又變作穿引字節的絲線。不必為每種數據類型重寫相似的篇章,模板早已在編譯時的晨光里,為每種可能悄悄謄抄好適配的詩句它像位沉默的譯者,將通用的邏輯轉化為各類型能讀懂的方言;又似位高明的導演,讓同一套劇情框架,在不同的數據演員身上演繹出千般模樣。那些被<>
包裹的期待,最終都會在編譯的煙火中,綻放出恰好貼合的形態 —— 這是程序員寫給機器的隱喻,讓代碼在嚴謹與靈活間,找到最詩意的平衡。
---------------------------------------------------------------------------------------------------------------------------------
博主的自言自語:
在博主眼里,模板其實就是鐵鍛里面的那些模具,用不同材料能制作出類似的工具。那么代碼就是材料,今天我要分享的就是模具。那么大聲的喊出今天要分享的是什么?
:模板? 模板? 模板,老大!!!。重要的事情說三遍。哎嘿!。@🤠
開始模板的學習:了解模板的底層
🚀? 我們在寫代碼的時候經常會遇到函數類型參數要接收不同類型的數據去執行,這樣就會有寫很多個類似函數的麻煩,但我們之前學的函數可以重載能一定程度上解決這一問題,但還是有不少的麻煩,不如用今天所要分享的模板,就拿交換功能的函數來說🚀:
函數重載:
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;
}
模板:
template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}
函數重載,模板。一眼望過去就能看到,模板的代碼量比函數重載代碼量少很多。
那么下面我們要來了解一下模板是怎么制作的。
泛型編程:編寫與類型無關的通用代碼,是代碼復用的一種手段。模板是泛型編程的基礎
模板是分兩類的:1.函數模板? 2.類模板。
1.函數模板:
template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}
上述代碼:template <typename T>是重中之重。我們要模板的原因就是類型,只要把識別類型的事交給編譯器去做就沒有我們的事了,直接做個甩手掌柜,起飛。
我們編譯器的工作:
:老大,老大!!!既然編譯器能識別類型,那么我就不要他識別,能不能行?哎嘿。
:我看你挺刑的。用不同類型的參數使用函數模板時,稱為函數模板的實例化。在我們的函數實例化中,是會有:隱式實例化和顯式實例化。
1. 隱式實例化:讓編譯器根據實參推演模板參數的實際類型。
2. 顯式實例化:在函數名后的<>中指定模板參數的實際類型。
那么之所以要顯示實例化,我們用代碼來理解:
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);/*該語句不能通過編譯,因為在編譯期間,當編譯器看到該實例化時,需要推演其實參類型通過實參a1將T推演為int,通過實參d1將T推演為double類型,但模板參數列表中只有一個T,編譯器無法確定此處到底該將T確定為int 或者 double類型而報錯注意:在模板中,編譯器一般不會進行類型轉換操作,因為一旦轉化出問題,編譯器就需要背黑鍋Add(a1, d1);*/// 此時有三種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化 3.定義多個TAdd(a, (int)d);
return 0;
}
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main(void)
{int a = 10;double b = 20.0;// 顯式實例化Add<int>(a, b);return 0;
}
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
void Test()
{Add(1, 2); Add(1, 2.0);
}
:那是不是我們之后就不用寫類型了,直接用模板了。
:額額額 》》》
// 專門處理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版本
}
2.類模板:
類模板定義格式:
template<class T1, class T2, ..., class Tn>
class 類模板名
{// 類內成員定義
};
我們就拿動態順序表:Vector
你可以將函數模板結合來看,因為類的組成里有成員函數,成員變量。
// 動態順序表
// 注意:Vector不是具體的類,是編譯器根據被實例化的類型生成具體類的模具
//這里只是講一下類模板。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;
}
1. 模板就是因類型不同而出的,所以與函數模板一樣,類模板,將類型換成T就行。
2. 函數在外類定義時,要加模板參數列表。
類的實例化:
// Vector類模板名,Vector<int>才是類
Vector<int> s1;
Vector<double> s2;
玩轉模板:晉升大大怪將軍。
1. 非類型模板參數
在前面已經了解了模板,但有些細節沒有,接下來就是玩轉模板時刻。
非類型模板參數就是相當于C語言里的define宏替換。就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用。
namespace xin
{// 定義一個模板類型的靜態數組template<class T, size_t N = 10>class array{public:T& operator[](size_t index){return _array[index];}const T& operator[](size_t index)const{return _array[index];}size_t size()const{return _size;}bool empty()const{return 0 == _size;}private:T _array[N];size_t _size;};
}
2. 模板的特化
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比較,結果錯誤return 0;
}
當我們用一個日期類對象的指針進行比較時,會出現錯誤。通常指針地址的大小是隨機的。
這就是我們要模板特化(寫一個特殊情況的)的原因。
這種情況有很多:1.?數組類型2.?函數指針類型3.?引用類型4.?智能指針5.?容器類型6.?基本類型與自定義類型的區分。
簡單的了解了一下模板特化,接下來是怎么實現。
函數模板特化:
4. 函數形參表:?必須要和模板函數的基礎參數類型完全相同,如果不同編譯器可能會報一些奇怪的錯誤。
// 函數模板 -- 參數匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
// 對Less函數模板進行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 調用特化之后的版本,而不走模板生成了return 0;
}
bool Less(Date* left, Date* right)
{return *left < *right;
}
類模板特化:
分為 1.全特化 2.?偏特化
1.全特化
template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
//全特化
template<>
class Data<int, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}
private:int _d1;char _d2;
};
void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}
格式:1.template<>? 2.類的后面加<? x,x? >里面的x是要用的類型。里面的類型就不是T了,而是知道的。
2.?偏特化
偏特化:任何針對模版參數進一步進行條件限制設計的特化版本
1.部分特化
// 將第二個參數特化為int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};
2.參數更進一步的限制
//兩個參數偏特化為指針類型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}private:
T1 _d1;T2 _d2;
};
//兩個參數偏特化為引用類型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data(const T1& d1, const T2& d2): _d1(d1), _d2(d2){cout<<"Data<T1&, T2&>" <<endl;}private:const T1 & _d1;const T2 & _d2; };
void test2 ()
{Data<double , int> d1; // 調用特化的int版本Data<int , double> d2; // 調用基礎的模板 Data<int *, int*> d3; // 調用特化的指針版本Data<int&, int&> d4(1, 2); // 調用特化的指針版本
}
其實所謂的特化就是將一些模板實現的函數或類無法應對特殊類型,因此要寫一個適合這個特殊類型的函數或類模板。
3.模板分離編譯
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}
為了解決這種編譯器沒看到模板函數的實力化(模板的 “按需生成” 機制與分離編譯的 “獨立編譯 + 鏈接” 模型不兼容):