C++模板完整版

顧得泉:個人主頁

個人專欄:《Linux操作系統》?《C++從入門到精通》??《LeedCode刷題》

鍵盤敲爛,年薪百萬!


一、泛型編程

如何實現一個通用的交換函數呢?

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;
}

使用函數重載雖然可以實現,但是有一下幾個不好的地方:

? ? ? ?1.重載的函數僅僅是類型不同,代碼復用率比較低,只要有新類型出現時,就需要用戶自己增加對應的函數

? ? ? ?2.代碼的可維護性比較低,一個出錯可能所有的重載均出錯

那能否告訴編譯器一個模子,讓編譯器根據不同的類型利用該模子來生成代碼呢?

???????如果在C++中,也能夠存在這樣一個模具,通過給這個模具中填充不同材料(類型),來獲得不同材料的鑄件(即生成具體類型的代碼),那將會節省許多頭發。巧的是前人早已將樹栽好,我們只需在此乘涼。

???????泛型編程:編寫與類型無關的通用代碼,是代碼復用的一種手段。模板是泛型編程的基礎


?二、函數模板

1.函數模板概念

???????函數模板代表了一個函數家族,該函數模板與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。

2.函數模板格式

???????template<typename T1, typename T2,…,typename Tn>

???????返回值類型 函數名(參數列表){}

template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}

注意:typename是用來定義模板參數關鍵字,也可以使用class(切記:不能使用struct代替class)

3.函數模板的原理

???????那么如何解決上面的問題呢?大家都知道,瓦特改良蒸汽機,人類開始了工業革命,解放了生產力。機器生產淘汰掉了很多手工產品。本質是什么,重復的工作交給了機器去完成。

???????有人給出了論調:懶人創造世界。

???????函數模板是一個藍圖,它本身并不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重復的事情交給了編譯器。

???????在編譯器編譯階段,對于模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。比如:當用double類型使用函數模板時,編譯器通過對實參類型的推演,將T確定為double類型,然后產生一份專門處理double類型的代碼,對于字符類型也是如此。

???????對于上述代碼,我們可以驗證他們調用的不是同一個函數,由此可以更好的理解模板

4.函數模板的實例化

???????用不同類型的參數使用函數模板時,稱為函數模板的實例化。模板參數實例化分為:隱式實例化和顯式實例化。

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); /*該語句不能通過編譯,因為在編譯期間,當編譯器看到該實例化時,需要推演其實參類型通過實參a1將T推演為int,通過實參d1將T推演為double類型,但模板參數列表中只有一個T,編譯器無法確定此處到底該將T確定為int 或者 double類型而報錯注意:在模板中,編譯器一般不會進行類型轉換操作,因為一旦轉化出問題,編譯器就需要背黑鍋Add(a1, d1);*/ // 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化Add(a, (int)d);return 0;
}

2.顯式實例化

int main(void)
{int a = 10;double b = 20.0;// 顯式實例化Add<int>(a, b);return 0;
}

???????如果類型不匹配,編譯器會嘗試進行隱式類型轉換,如果無法轉換成功編譯器將會報錯。

5.模板參數的匹配原則

1.一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化為這個非模板函數

// 專門處理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.對于非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數, 那么將選擇模板。

// 專門處理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函數
}

3.模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換


三、類模板

1.類模板的定義格式

template<class T1, class T2, ..., class Tn>
class 類模板名
{// 類內成員定義
};?
// 動態順序表
// 注意: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;
}

2.類模板的實例化

???????類模板實例化與函數模板實例化不同,類模板實例化需要在類模板名字后跟<>,然后將實例化的類型放在<>中即可,類模板名字不是真正的類,而實例化的結果才是真正的類。

// Vector類名,Vector<int>才是類型
Vector<int> s1;
Vector<double> s2;

四、非類型模板參數

模板參數分為:類型形參與非類型形參

???????類型形參:出現在模板參數列表中,跟在class或者typename之類的參數類型名稱。

???????非類型形參,就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用。

namespace goodQ
{// 定義一個模板類型的靜態數組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;};
}

注意:

???????浮點數、類對象以及字符串是不允許作為非類型模板參數的。
???????非類型的模板參數必須在編譯期就能確認結果。


五、模板的特化

1.概念

???????通常情況下,使用模板可以實現一些與類型無關的代碼,但對于一些特殊類型的可能會得到一些錯誤的結果,需要特殊處理,比如:實現了一個專門用來進行小于比較的函數模板

// 函數模板 -- 參數匹配
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絕對多數情況下都可以正常比較,但是在特殊場景下就得到錯誤的結果。上述示例中,p1指向的d1顯然小于p2指向的d2對象,但是Less內部并沒有比較p1和p2指向的對象內容,而比較的是p1和p2指針的地址,這就無法達到預期而錯誤。

???????此時,就需要對模板進行特化。

???????即:在原模板類的基礎上,針對特殊類型所進行特殊化的實現方式。模板特化中分為函數模板特化與類模板特化。

2.函數模板特化

函數模板的特化步驟:

? ? ? ? 1.必須要先有一個基礎的函數模板
????????2.關鍵字template后面接一對空的尖括號<>
????????3.函數名后跟一對尖括號,尖括號中指定需要特化的類型
????????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;
}

???????該種實現簡單明了,代碼的可讀性高,容易書寫,因為對于一些參數類型復雜的函數模板,特化時特別給出,因此函數模板不建議特化。

3.類模板特化

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;
}

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;
};

? ②參數更進一步的限制

???????偏特化并不僅僅是指特化部分參數,而是針對模板參數更進一步的條件限制所設計出來的一個特化版本。

//兩個參數偏特化為指針類型
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); // 調用特化的指針版本
}

?六、模板分離編譯

1.什么是分離編譯

???????一個程序(項目)由若干個源文件共同實現,而每個源文件單獨編譯生成目標文件,最后將所有目標文件鏈接起來形成單一的可執行文件的過程稱為分離編譯模式。

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.解決方法

??????1.將聲明和定義放到一個文件 “xxx.hpp” 里面或者xxx.h其實也是可以的。推薦使用這種。
??????2.模板定義的位置顯式實例化。這種方法不實用,不推薦使用。

【分離編譯擴展閱讀】http://blog.csdn.net/pongba/article/details/19130

4.模板總結

【優點】

1.模板復用了代碼,節省資源,更快的迭代開發,C++的標準模板庫(STL)因此而產生
2.增強了代碼的靈活性

【缺陷】

1.模板會導致代碼膨脹問題,也會導致編譯時間變長
2.出現模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤


結語:C++關于模板的分享到這里就結束了,希望本篇文章的分享會對大家的學習帶來些許幫助,如果大家有什么問題,歡迎大家在評論區留言,最后祝大家新的一年里學業有成,天天開心~~~?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/719135.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/719135.shtml
英文地址,請注明出處:http://en.pswp.cn/news/719135.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

抖店入駐費用是多少?新手入駐都有哪些要求?2024費用明細!

我是電商珠珠 我做電商做了將近五年&#xff0c;做抖店做了三年多&#xff0c;期間還帶著學員一起做店。 今天&#xff0c;就來給大家詳細的講一下在抖音開店&#xff0c;需要多少費用&#xff0c;最低需要投入多少。 1、營業執照200元左右 就拿個體店舉例&#xff0c;在入…

hook函數——useReducer

目錄 1.useReducer定義2.useReducer用法3.useState和useReducer區別 1.useReducer定義 const [state, dispatch] useReducer(reducer, initialArg, init?) reducer&#xff1a;用于更新 state 的純函數。參數為 state 和 action&#xff0c;返回值是更新后的 state。state …

這波操作看麻了!十億行數據,從71s到1.7s的優化之路。

節期間關注到了一個關于 Java 方面的比賽&#xff0c;很有意思。由于是開源的&#xff0c;我把項目拉下來試圖學&#xff08;白&#xff09;習&#xff08;嫖&#xff09;別人的做題思路&#xff0c;在這期間一度讓我產生了一個自我懷疑&#xff1a; 他們寫的 Java 和我會的 Ja…

解鎖軟件管理新篇章,Allegro許可證使用規定全解

在數字化經濟的時代&#xff0c;軟件已經成為企業運營的關鍵要素。然而&#xff0c;軟件的使用往往伴隨著一系列的合規性問題&#xff0c;導致企業面臨潛在的風險和成本。Allegro許可證作為業界領先的軟件解決方案提供商&#xff0c;為企業提供全面的許可證使用規定&#xff0c…

每日一題——LeetCode1576.替換所有的問號

方法一 3個字母原則 把&#xff1f;替換為和他左右都不相等的字符&#xff0c;那么找3個字符abc&#xff0c;&#xff1f;總能替換為abc中的一個字符&#xff0c;遍歷字符串找到所有&#xff1f;&#xff0c;再遍歷abc把&#xff1f;替換為abc中的一個字符 var modifyString …

解析 openGauss 的 AutoVacuum 機制及優化策略

前言 在 openGauss 數據庫中&#xff0c;AutoVacuum 機制是一個關鍵的自動化功能&#xff0c;用于管理表的空間和性能。AutoVacuum 通過定期清理過時數據和更新統計信息&#xff0c;幫助數據庫管理員維護數據庫的性能和穩定性。 為什么需要 AutoVacuum&#xff1f; 了解AutoV…

JAVA內存模型與JVM內存結構

注意區分Java內存模型&#xff08;Java Memory Model&#xff0c;簡稱JMM&#xff09;與Jvm內存結構&#xff0c;前者與多線程相關&#xff0c;后者與JVM內部存儲相關。本文會對兩者進行簡單介紹。 一、JAVA內存模型(JMM) 1. 概念 說來話長&#xff0c;由于在不同硬件廠商和…

No matching version found for @babel/traverse@^7.24.0.

問題&#xff1a; npm安裝 依賴失敗&#xff0c;找不到所需依賴。 原因&#xff1a; npm鏡像源中沒有該依賴。&#xff08;大概率是因為依賴最近剛更新&#xff0c;當前鏡像源沒有同步&#xff09; 解決&#xff1a; 查看自己的npm鏡像&#xff1a;npm config get registry…

機器學習-面經(part2)

3. 驗證方式 3.1什么是過擬合?產生過擬合原因? 定義:指模型在訓練集上的效果很好,在測試集上的預測效果很差 數據有噪聲 訓練數據不足,有限的訓練數據 訓練模型過度導致模型非常復雜3.2 如何避免過擬合問題? 3.3 什么是機器學習的欠擬合?產生原…

D4890可應用在對講機上,采用 SOP8/MSOP8兩種封裝形式

D4890 目前客戶主要使用在對講機上&#xff0c;電壓范圍2.2V &#xff5e; 5.5V之間&#xff0c;輸出功率&#xff08;THDN1%&#xff09;1.0W/8Ω 5.0V。采用 SOP8/MSOP8兩種封裝形式。 2、推薦的應用線路圖如下&#xff1a; 3、實際測試輸出波形如下&#xff08;VCC4.5V&…

Web Component 轉圖片

一、HTML 轉圖片 目前&#xff0c;常見的開源的能夠將 HTML 轉換為圖片有html2canvas、dom-to-image&#xff0c;大部分場景下&#xff0c;這些開源庫都能很友好的處理。 HTML 轉圖片的實現原理&#xff0c;通常分為兩種&#xff1a;svg 與 canvas。今天主要討論下 svg 的場景…

Flutter中使用Dio庫封裝網絡請求服務工具類

在Flutter應用程序中&#xff0c;進行網絡請求是非常常見的任務。Dio是一個強大的、易于使用的Dart包&#xff0c;用于處理HTTP請求。本篇博客將介紹如何封裝Dio庫&#xff0c;以及如何在Flutter應用中進行網絡請求并取消請求。 什么是Dio&#xff1f; Dio是一個基于Dart語言…

解決android studio build Output中文亂碼

1.效果如下所示&#xff1a; 代碼運行報錯的時候&#xff0c;Build Output報的錯誤日志中中文部分出現亂碼&#xff0c;導致看不到到底報的什么錯。 2.解決辦法如下&#xff1a; 點擊Android studio開發工具欄的Help-Edit Custom VM Options....&#xff0c;Android studio會…

springboot微服務中集成了mybatis的服務引入了其他集成了mybatis的服務此時調用引入的服務中的某個mapper接口時報沒有注入

在啟動類上加引入的服務中的mapper路徑&#xff0c;在配置文件中將mapperLocations的值改為classpath*:mapper/.xml&#xff1a; MapperScan(basePackages {"com.ruoyi..mapper"}) 和 mapperLocations: classpath*:mapper/*.xml 是 MyBatis 在 Spring Boot 中配置 M…

AutoGPT實現原理

AutoGPT是一種利用GPT-4模型的自動化任務處理系統&#xff0c;其主要特點包括任務分配、多模型協作、互聯網訪問和文件讀寫能力以及上下文聯動記憶性。其核心思想是通過零樣本學習&#xff08;Zero Shot Learning&#xff09;讓GPT-4理解人類設定的角色和目標&#xff0c;并通過…

端口號被占用時的解決辦法

1、查看端口占用的進程號 netstat -ano |findstr 8080 2、 找到占用端口的程序 tasklist |findstr 2264 3、kill端口 taskkill /pid 2264 /f

文物預防性保護方案整體結構及軟件介紹

?文物預防性保護監測與調控系統整體是構架在商業級技術平臺上的多層綜合性應用,采用分布式部署的模塊化設計,以智能監測終端及高精傳感器為核心的感知系統。系統通過以下的層次結構協同工作完成全面的監控與調控功能&#xff1a; 1)系統依靠文物監測調控模型作為運行核心&…

基于springboot+vue的校園愛心捐贈互助管理系統(源碼+論文)

目錄 前言 一、功能設計 二、功能實現 三、庫表設計 四、論文 前言 隨著經濟水平和生活水平的提高在校大學生在校需要處理的物品也在不斷增加&#xff0c;同時校園內還存在很多貧困生&#xff0c;可以通過線上平臺實現資源的整合和二次利用&#xff0c;通過線上平臺求助信…

護眼燈有效果嗎怎么樣?推薦五款值得入手的護眼臺燈

隨著護眼臺燈被越來越多的人解鎖新的護眼攻略&#xff0c;它的產品熱度也越來越高&#xff0c;而且光線柔和&#xff0c;是一款非常不錯的照明用具。但是也有不少用戶反饋買到的護眼臺燈效果不好&#xff0c;有時候還會覺得刺眼&#xff0c;有些不合格的臺燈使用時間一久還會散…

動態IP代理技術在網絡爬蟲中的實際使用

目錄 一、動態IP代理技術概述 二、動態IP代理技術的優勢 三、動態IP代理技術的實際應用 四、注意事項 五、案例分析 六、結論 隨著互聯網的迅猛發展&#xff0c;網絡爬蟲成為了獲取信息、分析數據的重要工具。然而&#xff0c;在進行大規模爬取時&#xff0c;爬蟲常常面臨…