一、非類型模板參數
模板參數? 分為? 類型形參與? 非類型形參。類型形參:出現在模板參數列表中,跟在?class?或者 typename 之類的參數類型名稱。非類型形參,就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常 量來使用。注意:1.?浮點數、類對象以及字符串是不允許作為非類型模板參數的。2.?非類型的模板參數必須在編譯期就能確認結果。3. 類型形參 和 非類型形參都可以給缺省值。
?非類型形參有什么用 ??
用宏定義的N 實現的靜態棧對比 :
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stack>
using namespace std;#define N 20template<class T>
class Stack
{
private:T _a[N];int _top;
};int main()
{Stack<int> st1; //20 return 0;
}
?
此時建立的Stack 的大小只能為N , N如果為 20 , 僅能建立大小為 20 的靜態棧 。如果借助非類型形參? , 則靜態棧建立的大小就變得靈活了 。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stack>
using namespace std;template<class T,size_t N>
class Stack
{
private:T _a[N];int _top;
};int main()
{Stack<int,20> st1; //20 Stack<int, 2000> st2; //2000return 0;
}
非類型的形參一般來說 , 給整型常量 , 其他的可能不支持 , 浮點常量在C++20支持 。
二、模板的特化
2.1 概念
通常情況下,使用模板可以實現一些與類型無關的代碼,但對于一些特殊類型的可能會得到一些 錯誤的結果,需要特殊處理,比如:實現了一個專門用來進行小于比較的函數模板
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stack>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 函數模板 -- 參數匹配
template<class T>
bool Less(T left, T 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;
}
?
此時 , 就需要對模板進行特化 , 即 : 在原模板類的基礎上 , 針對特殊類型所進行特殊化的實現方式 。 模板特化中分為函數模板特化 與 類模板特化 。
2.2 函數模板特化
函數模板特化的步驟:
- 必須要有一個基礎的函數模板
- 關鍵字?template? 后面接 一對 空的尖括號 <>
- 函數名?后跟一對尖括號 , 尖括號中指定需要特化的類型 。
- 函數形參表 : 必須要和模板函數的基礎參數類型完全相同 , 如果不同編譯器可能會報一些奇怪的錯誤 。?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stack>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}// 函數模板 -- 參數匹配
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;
}
?該種實現簡單明了 , 代碼的可讀性高 , 容易書寫 , 因為對于一些參數類型復雜的函數模板 , 特化時會比較復雜?。?因此函數模板不建議特化 。?
2.3 類模板特化
2.3.1 全特化
全特化即是將? 模板參數列表中? 所有的參數都確定化。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stack>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}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;
}
?
2.3.2 偏特化?
1) 任何針對模版參數進一步進行 條件限制 設計的特化版本。:
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//兩個參數偏特化為指針類型
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); // 調用特化的指針版本
}
?
2)部分特化:將模板參數類表中的? 一部分參數? 特化
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};// 將第二個參數特化為int
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};
?
三、模板分離編譯
3.1 什么是分離編譯
一個程序(項目)由若干個源文件共同實現,而每個源文件單獨編譯生成目標文件 , 最后將所有目標文件鏈接起來形成單一的可執行文件的過程稱為分離編譯模式 。?
3.2 模板的分離編譯
模板的聲明和定義不分離 !!!
假如有以下場景 , 模板的聲明與定義分離開 , 在頭文件中進行聲明 , 源文件中完成定義:
// 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;
}
為什么鏈接錯誤 ? (通常是指有聲明無定義)?
3.3 解決方法
?1. 將聲明和定義放到一個文件 “ xxx.hpp ” 里面 或者 xxx.h 其實也是可以的 。(推薦)
?2. 模板定義的位置顯示實例化 。 這種方法不實用,不推薦使用 。?
3.4 模板總結
【優點】?
- 模板復用了代碼,節省資源,更快迭代開發,C++的標準模板庫(STL)因此產生。
- 增強了代碼的靈活性
【缺點】
- 模板會導致代碼膨脹問題,也會導致編譯時間變長
- 出現模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤