W...Y的主頁 ???
代碼倉庫分享???
?
??前言:?
在C++的世界里,模板是一種強大而神奇的工具,宛如編程的瑰寶匣,蘊藏著無限的可能性。它們不僅能夠讓我們編寫通用的代碼,還能夠在編譯時實現(xiàn)類型安全的抽象。然而,模板的奧秘并非易見,它們像是編碼世界中的魔法咒語,需要睿智者的智慧和技巧才能真正駕馭。
在這段旅程中,我們將探索C++模板的奧秘,解鎖其妙用技巧的寶藏。我們將揭開模板編程的神秘面紗,探索如何借助模板實現(xiàn)泛型編程、容器類、算法以及更多令人驚嘆的功能。讓我們一同穿越這個編程的魔法門,發(fā)現(xiàn)模板編程的精妙所在,探索其中隱藏的無盡可能性。準備好了嗎?讓我們踏上這段模板編程之旅,探尋其中的精彩和奇跡。
目錄
什么是模板
?非類型模板參數(shù)
?模板的特化
概念
?函數(shù)模板特化
類模板特化
全特化
偏特化
模板分離編譯
什么是分離編譯
模板的分離編譯
解決方法
模板總結
什么是模板
在C++中,模板是一種泛型編程(Generic Programming)的工具,它允許程序員編寫通用的、與數(shù)據(jù)類型無關的代碼。使用模板,你可以編寫函數(shù)或類,使其能夠適用于多種數(shù)據(jù)類型而不需要重復編寫多份相似的代碼。
C++模板的兩種主要形式是函數(shù)模板和類模板:
函數(shù)模板: 函數(shù)模板允許你編寫一個通用的函數(shù),其中的某些類型或值可以是參數(shù)化的。例如,你可以編寫一個通用的排序函數(shù),可以對整數(shù)數(shù)組、浮點數(shù)數(shù)組或其他類型的數(shù)組進行排序,而不需要為每種類型都編寫一個獨立的排序函數(shù)。
template <typename T>
void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
這里,typename T
表示這是一個模板,并且 T
是一個占位符,代表了實際的數(shù)據(jù)類型。
類模板: 類模板允許你編寫通用的類,其中的某些成員可以是參數(shù)化的。例如,你可以編寫一個通用的棧類,可以存儲不同類型的元素。
template <typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T &element) {
elements.push_back(element);
}
T pop() {
if (!elements.empty()) {
T top = elements.back();
elements.pop_back();
return top;
} else {
// Handle empty stack
// ...
}
}
};
這里,template<typename T>表示這是一個類模板,T
是一個占位符,代表了實際的數(shù)據(jù)類型。
通過使用模板,可以實現(xiàn)更加靈活和可重用的代碼,因為它使得代碼可以獨立于具體的數(shù)據(jù)類型。在標準庫中,許多常見的數(shù)據(jù)結構和算法都是通過模板實現(xiàn)的。
?非類型模板參數(shù)
模板參數(shù)分類類型形參與非類型形參。
類型形參即:出現(xiàn)在模板參數(shù)列表中,跟在class或者typename之類的參數(shù)類型名稱。
非類型形參:就是用一個常量作為類(函數(shù))模板的一個參數(shù),在類(函數(shù))模板中可將該參數(shù)當成常量來使用。?
類型模板參數(shù)就是通過定義類型作為參數(shù)的,上面的代碼都是類型形參。
而非類型模板是什么呢?
當我們定義一個數(shù)組時使用#define可以進行宏定義,當我們創(chuàng)建一個數(shù)組時就可以數(shù)量為20的數(shù)組。?
#include <vector>
using namespace std;
#define n 20
//類型模板參數(shù)
//非類型模板參數(shù) -- 整形常量
template<class T>
class Array
{
private:
T _a[n];
};
int main()
{
Array<int> a1;
Array<double> a2;
return 0;
}
但是當我們想要進行創(chuàng)建一個空間個數(shù)為10的數(shù)組時就無法去創(chuàng)建,只能重新在定義一個模板進行。
非類型模板參數(shù)可以代替宏定義?。?!這時非類型模板就可以起到用處:
template<class T, size_t n = 20>
class Array
{
private:
T _a[n];
};
int main()
{
Array<int, 10> a1;
Array<int> a;
Array<double, 100> a2;
return 0;
}
注意:
1. 浮點數(shù)、類對象以及字符串是不允許作為非類型模板參數(shù)的。
2. 非類型的模板參數(shù)必須在編譯期就能確認結果。
C++11中在STL中添加了新的容器,array容器。
這個容器就是數(shù)組,使用了非類型模板參數(shù),所以在函數(shù)接口中就沒有rserve與rsize了。那這個容器好不好呢??
int arr[10];
array<int, 10> a1;
//這兩個是相同對標的
他們有什么區(qū)別呢?就是在越界檢測方式不同,數(shù)組的檢測方式是抽查寫,而array是全面檢測。但是這個東西不是很好,我們都可以使用vector了,為什么還要使用array呢?這里只是給大家做一個了解。
?模板的特化
概念
通常情況下,使用模板可以實現(xiàn)一些與類型無關的代碼,但對于一些特殊類型的可能會得到一些錯誤的結果,需要特殊處理,比如:實現(xiàn)了一個專門用來進行小于比較的函數(shù)模板
// 函數(shù)模板 -- 參數(shù)匹配
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;
}
可以看到,Less絕對多數(shù)情況下都可以正常比較,但是在特殊場景下就得到錯誤的結果。上述示例中,p1指向的d1顯然小于p2指向的d2對象,但是Less內(nèi)部并沒有比較p1和p2指向的對象內(nèi)容,而比較的是p1和p2指針的地址,這就無法達到預期而錯誤。
此時,就需要對模板進行特化。即:在原模板類的基礎上,針對特殊類型所進行特殊化的實現(xiàn)方式。模板特化中分為函數(shù)模板特化與類模板特化。?
?函數(shù)模板特化
函數(shù)模板的特化步驟:
1. 必須要先有一個基礎的函數(shù)模板
2. 關鍵字template后面接一對空的尖括號<>
3. 函數(shù)名后跟一對尖括號,尖括號中指定需要特化的類型
4. 函數(shù)形參表:?必須要和模板函數(shù)的基礎參數(shù)類型完全相同,如果不同編譯器可能會報一些奇怪的錯誤。
// 函數(shù)模板 -- 參數(shù)匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 對Less函數(shù)模板進行特化
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; // 調(diào)用特化之后的版本,而不走模板生成了
return 0;
}
?注意:一般情況下如果函數(shù)模板遇到不能處理或者處理有誤的類型,為了實現(xiàn)簡單通常都是將該函數(shù)直接給出。
bool Less(Date* left, Date* right)
{
return *left < *right;
}
該種實現(xiàn)簡單明了,代碼的可讀性高,容易書寫,因為對于一些參數(shù)類型復雜的函數(shù)模板,特化時特別給出,因此函數(shù)模板不建議特化。
類模板特化
全特化
全特化即是將模板參數(shù)列表中所有的參數(shù)都確定化。
template<class T>
bool Less(T left, T right)
{
return left < right;
}
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; // 可以比較,結果錯誤
int* p3 = new int(1);
int* p4 = new int(2);
cout << Less(p3, p4) << endl; // 可以比較
return 0;
}
全特化是對模板一種特殊處理,因為模板中有一部分內(nèi)容的比較結果與現(xiàn)實不符,我們必須特殊處理。所以必須有模板才能有特化類模板。
偏特化
偏特化:任何針對模版參數(shù)進一步進行條件限制設計的特化版本。比如對于以下模板類:
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
偏特化有以下兩種表現(xiàn)方式:
部分特化
將模板參數(shù)類表中的一部分參數(shù)特化:
// 將第二個參數(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ù)偏特化為指針類型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//兩個參數(shù)偏特化為引用類型
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; // 調(diào)用特化的int版本
Data<int , double> d2; // 調(diào)用基礎的模板
Data<int *, int*> d3; // 調(diào)用特化的指針版本
Data<int&, int&> d4(1, 2); // 調(diào)用特化的指針版本
}
模板分離編譯
什么是分離編譯
一個程序(項目)由若干個源文件共同實現(xiàn),而每個源文件單獨編譯生成目標文件,最后將所有目標文件鏈接起來形成單一的可執(zhí)行文件的過程稱為分離編譯模式。
模板的分離編譯
假如有以下場景,模板的聲明與定義分離開,在頭文件中進行聲明,源文件中完成定義:
// 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;
}
?分析:
?func函數(shù)是有地址的,所以我們在鏈接時可以找到,但是模板卻沒有,所以編譯器在開辟函數(shù)棧幀時就不知道開多大空間,所以無法進行鏈接。
解決方法
1. 將聲明和定義放到一個文件 "xxx.hpp" 里面或者xxx.h其實也是可以的。推薦使用這種。
#include <vector>
using namespace std;
template<class T>
T Add(const T& left, const T& right);
void func();
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
//聲明和定義放到一起,直接就可以實例化,編譯時就有地址,不需要鏈接
2. 模板定義的位置顯式實例化。這種方法不實用,不推薦使用。?
#define _CRT_SECURE_NO_WARNINGS 1
#include "Func.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void func()
{
cout << "void func()" << endl;
}
template
double Add<double>(const double& left, const double& right);
template
int Add<int>(const int& left, const int& right);
?但是顯示實例化只能聲明一種類型,當我們需要另一種類型時就要重新實例化一個新類型。
模板總結
【優(yōu)點】
1. 模板復用了代碼,節(jié)省資源,更快的迭代開發(fā),C++的標準模板庫(STL)因此而產(chǎn)生
2. 增強了代碼的靈活性
【缺陷】
1. 模板會導致代碼膨脹問題,也會導致編譯時間變長
2. 出現(xiàn)模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤文章來源:http://www.zghlxwxcb.cn/news/detail-758731.html
在這段模板之旅的終點,我們領略了C++模板的神奇之處,仿佛探索了編程的奇境。通過學習模板的一級運用技巧,我們擁有了一把打開泛型編程之門的鑰匙,能夠以更加通用和靈活的方式編寫代碼。文章來源地址http://www.zghlxwxcb.cn/news/detail-758731.html
到了這里,關于[C++歷練之路]C++模板還能這么玩,已經(jīng)走了好多彎路,后悔沒有早點學會到。的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!