做網(wǎng)站可以做什么seo推廣優(yōu)化
👦個人主頁:@Weraphael
?🏻作者簡介:目前學習C++和算法
??專欄:C++航路
🐋 希望大家多多支持,咱一起進步!😁
如果文章對你有幫助的話
歡迎 評論💬 點贊👍🏻 收藏 📂 加關(guān)注?
前言
初階模板地址:點擊跳轉(zhuǎn)
目錄
- 前言
- 一、typename和class的區(qū)別
- 二、非類型模板參數(shù)
- 2.1 概念
- 2.2 實際例子:array容器
- 三、模板特化
- 3.1 概念
- 3.2 函數(shù)模板特化
- 3.3 類模板特化
- 3.3.1 全特化
- 3.3.2 偏特化
- 四、模板分離編譯問題
- 4.1 什么是分離編譯
- 4.2 模板的分離編譯
- 4.3 解決方法
- 五、小結(jié)
一、typename和class的區(qū)別
在以前博客我們說過,定義模板參數(shù)關(guān)鍵字可以用typename
,也可以用class
,它們是沒有區(qū)別的,可是真的沒有區(qū)別嗎?來看看以下這個例子
假設(shè)要打印容器的數(shù)據(jù),要封裝一個打印函數(shù)(以迭代器的方式)
#include <iostream>
#include <vector>
using namespace std;void Print(const vector<int>& v)
{vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << ' ';it++;}cout << endl;
}int main()
{vector<int> v{ 10,20,30,40,50 };Print(v);return 0;
}
【輸出結(jié)果】
以上代碼雖然可以正常輸出,但它只能打印vector<int>
類型容器的數(shù)據(jù),若要打印vector<double>
,又或者是list
容器的數(shù)據(jù),那么這樣就寫死了,有人想可以用函數(shù)模板。
代碼如下:
#include <iostream>
#include <vector>
using namespace std;// 把class換成typename也是可以的
template<class Container>
void Print(const Container& v)
{Container::const_iterator it = v.begin();while (it != v.end()){cout << *it << ' ';it++;}cout << endl;
}int main()
{vector<int> v{ 10,20,30,40,50 };Print(v);return 0;
}
【輸出結(jié)果】
使用函數(shù)模板后發(fā)現(xiàn)以上代碼報錯了,提示說需要在Container
前加上typename
那么為什么必須要加上typename
呢?
這是因為編譯器在編譯的時候是從上往下的,當編譯到Container::const_iterator it
時,Container還沒實例化,那么此時編譯器區(qū)分不了Container
是類型還是類對象(靜態(tài)成員變量Container::const_iterator
)。vector<int>
是實例化出來的,加上域作用限定符::
去找其內(nèi)嵌類型(迭代器),所以不會報錯。因此,編譯器要求加上typename
告訴Container
是類型。
二、非類型模板參數(shù)
2.1 概念
模板參數(shù)分為:類型形參與非類型形參
-
類型形參:出現(xiàn)在模板參數(shù)列表中,跟在
class
或者typename
之類的參數(shù)類型名稱。 -
非類型形參:將常量作為類(函數(shù))模板的一個參數(shù),在類(函數(shù))模板中可將該參數(shù)當成常量來使用。
舉個例子:定義一個模板類型的靜態(tài)數(shù)組
#include <iostream>
#include <vector>
using namespace std;// size_t N = 10 - 非類型形參
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;
};
需要注意的是,非類型模板參數(shù)必須滿足以下兩點:
- 必須是常量,不可被修改
- 必須是整型。整型家族有:
char
、short
、bool
、int
、long
、long long
等
因此可以得出,非類型模板參數(shù)一般是用來定義一個數(shù)組的大小的
2.2 實際例子:array容器
在C++11標準中,引入了一個容器array
,它的底層使用了非類型模板參數(shù),是一個真正意義上的泛型數(shù)組,這個是用來對標傳統(tǒng)數(shù)組的。
#include <iostream>
#include <array>
using namespace std;int main()
{int arr[10] = { 0 }; //傳統(tǒng)數(shù)組array<int, 10> _array; //array容器// 讀arr[12]; _array[12];// 寫arr[12] = 0; _array[12] = 10;return 0;
}
對比傳統(tǒng)數(shù)組:
array
也并沒有進行初始化。array
新數(shù)組對于越界讀、寫檢查更為嚴格。傳統(tǒng)數(shù)組越界讀寫,不會發(fā)生報錯;而array
數(shù)組則會報錯。
雖然對越界行為檢查嚴格 ,但在實際開發(fā)中,很少使用array
容器,因為它對標傳統(tǒng)數(shù)組,連初始化都沒有,而vector
也是類似于數(shù)組的容器,在功能和實用性上可以全面碾壓,并且 array
使用的是棧區(qū)上的空間,會存在棧溢出問題,因此可以說array
是一個雞肋的容器。
三、模板特化
3.1 概念
模板特化顧名思義就是對模板(泛型思想)的特殊化處理 。模板特化中分為函數(shù)模板特化與類模板特化。
通常情況下,使用模板可以實現(xiàn)一些與類型無關(guān)的代碼,但對于一些特殊類型的可能會得到一些錯誤的結(jié)果,需要特殊處理。
比如:實現(xiàn)一個專門用來進行小于比較的函數(shù)模板
#include <iostream>
using namespace std;template<class T>
bool Less(T x, T y)
{return x < y;
}int main()
{int a = 1;int b = 2;cout << Less(a, b) << endl; // 可以比較,結(jié)果正確int* p1 = &a;int* p2 = &b;cout << Less(p1, p2) << endl; // 可以比較,結(jié)果錯誤return 0;
}
【輸出結(jié)果】
上述示例中,p1
指向的a
顯然小于p2
指向的b
,但是Less
內(nèi)部并沒有比較p1
和p2
指向的對象內(nèi)容,而比較的是p1
和p2
指針的地址。
因此,就需要對模板進行特化。即在原模板函數(shù)的基礎(chǔ)上,針對特殊類型所進行特殊化的實現(xiàn)方式。
3.2 函數(shù)模板特化
函數(shù)模板的特化步驟:
- 必須要先有一個基礎(chǔ)的函數(shù)模板
- 關(guān)鍵字
template
后面接一對空的尖括號<>
- 函數(shù)名后跟一對尖括號,尖括號中指定需要特化的類型
- 函數(shù)形參必須要和基礎(chǔ)的模板函數(shù)的基礎(chǔ)參數(shù)類型完全相同
#include <iostream>
#include <vector>
using namespace std;// 必須要先有一個基礎(chǔ)的函數(shù)模板
template<class T>
bool Less(T x, T y)
{return x < y;
}// 對Less函數(shù)模板進行特化
template<> // 關(guān)鍵字template后面接一對空的尖括號<>
// 函數(shù)名后跟一對尖括號,尖括號中指定需要特化的類型
bool Less<int*>(int* x, int* y) // 函數(shù)形參必須要和基礎(chǔ)的模板函數(shù)的基礎(chǔ)參數(shù)類型完全相同
{return *x < *y;
}int main()
{int a = 1;int b = 2;cout << Less(a, b) << endl; // 可以比較,結(jié)果正確int* p1 = &a;int* p2 = &b;cout << Less(p1, p2) << endl; // 可以比較,結(jié)果錯誤return 0;
}
【輸出結(jié)果】
不過對于函數(shù)模板特化來說,存在一個更加方便的東西:函數(shù)重載同樣也能解決特殊需求
bool Less(int* x, int* y)
{return *x < *y;
}
3.3 類模板特化
模板特化主要用在類模板中,它可以在泛型思想之上解決大部分特殊問題,并且類模板特化還可以分為:全特化
和偏特化
3.3.1 全特化
全特化指將原模板參數(shù)列表中所有的參數(shù)都確定化
注意:在進行全特化前
- 需要存在最基本的泛型模板
- 全特化模板中的模板參數(shù)可以不用寫
- 需要在類名之后,指明具體的參數(shù)類型,否則無法實例化出對象
// 原模板
template<class T1, class T2>
class Test
{
public:Test(const T1& t1, const T2& t2):_t1(t1), _t2(t2){cout << "Test(const T1& t1, const T2& t2)" << endl;}private:T1 _t1;T2 _t2;
};// 全特化后的模
// 將原模板參數(shù)列表中所有的參數(shù)都確定化
template<>
class Test<int, char>
{
public:Test(const int& t1, const char& t2):_t1(t1), _t2(t2){cout << "Test<int, char>" << endl;}private:int _t1;char _t2;
};int main()
{Test<int, int> T1(1, 2);Test<int, char> T2(20, 'c');return 0;
}
調(diào)用時會優(yōu)先選擇更為匹配的類模板
3.3.2 偏特化
偏特化有以下兩種表現(xiàn)方式
- 部分特化
顧名思義只特化一部分模板參數(shù)
// 原模板
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }private:T1 _d1;T2 _d2;
};// 偏特化// 將第二個參數(shù)特化為int
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};
- 參數(shù)更進一步的限制
即不僅僅指特化部分參數(shù),而是針對模板參數(shù)的更進一步的條件限制所設(shè)計出來的一個特化版本
借助偏特化解決指針無法正常比較問題
class Date
{
public:Date(int year = 1970, 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);}private:int _year;int _month;int _day;
};//原來的比較模板
template<class T>
class Less
{
public:bool operator()(T x, T y) const{return x < y;}
};//偏特化后的比較模板
template<class T>
class Less<T*>
{
public:bool operator()(T* x, T* y) const{return *x < *y;}
};int main()
{Date d1 = { 2018, 4, 10 };Date d2 = { 2023, 5, 10 };cout << "d1 < d2: " << Less<Date>()(d1, d2) << endl;cout << "&d1 < &d2: " << Less<Date*>()(&d1, &d2) << endl;int a = 1;int b = 2;cout << "&a < &b: " << Less<int*>()(&a, &b) << endl;return 0;
}
四、模板分離編譯問題
4.1 什么是分離編譯
一個程序(項目)由若干個源文件共同實現(xiàn),而每個源文件單獨編譯生成目標文件,最后將所有目標文件鏈接起來形成單一的可執(zhí)行文件的過程稱為分離編譯模式。
4.2 模板的分離編譯
假如有以下場景,模板的聲明與定義分離開,在頭文件中進行聲明,源文件中完成定義:
【程序結(jié)果】
出現(xiàn)了鏈接錯誤!!!
【分析】
代碼從文本變?yōu)榭蓤?zhí)行程序所需要的步驟:
- 預處理:頭文件展開、宏替換、條件編譯、刪除注釋,生成純凈的C/C++代碼
- 編譯:語法/詞法/語義分析,錯誤檢查無誤后生成匯編代碼。注意:頭文件不參與編譯,編譯器對工程中的多個源文件是分離開并且單獨編譯的
- 匯編:生成符號表,生成二進制指令
- 鏈接:將符號表進行合并,并處理地址問題
當模板的聲明和定義分離時,在realize.cpp
中,由于是 泛型,編譯器無法確定函數(shù)原型(實例化),因此無法生成函數(shù),也就無法獲得函數(shù)地址,在進行鏈接時,無法在符號表中找到目標地址進行跳轉(zhuǎn),導致鏈接錯誤
除了模板以外,還有一個很常見的連接錯誤,有函數(shù)聲明,卻沒有定義
4.3 解決方法
- 將聲明和定義放到一個文件(推薦使用這種)
template<class T>
T add(const T x, const T y)
{return x + y;
}
- 在函數(shù)定義時進行模板特化,編譯時生成地址以進行鏈接(不推薦使用)
template<>
int add(const int x, const int y)
{return x + y;
}
五、小結(jié)
模板的優(yōu)點:
- 模板復用了代碼,節(jié)省資源,更快的迭代開發(fā),C++的標準模板庫(
STL
)因此而產(chǎn)生 - 增強了代碼的靈活性
模板的缺陷:
- 模板會導致代碼膨脹問題,也會導致編譯時間變長
- 出現(xiàn)模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤