C++(模板進階)


目錄

前言:

本章學習目標:

1.非類型模版參數?

1.1使用方法?

1.2注意事項

1.3?實際引用

2.模版特化?

?2.1概念

2.2函數模板特化

?2.3類模板特化

2.3.1全特化

2.3.2偏特化?

?3.模版分離編譯

?編輯?3.1失敗原因

?編輯?3.2解決方案

4 總結?



前言:

本章節是在學習完STL之后,對高階模版進行的總結,模板給泛型編程注入了靈魂,模板提高了程序的靈活性,模板包括:非類型模版參數、全特化、偏特化等,同時本文還會對模板聲明和定義不能分離的問題做出介紹。

本章學習目標:

1. 非類型模板參數
2. 類模板的特化
3. 模板的分離編譯

1.非類型模版參數?

模板參數分類 :類型形參與非類型形參。
類型形參即:? ? 出現在模板參數列表中,跟在class或者typename之類的參數類型名稱。
非類型形參:? ? 就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用。

1.1使用方法?

非類型模版參數 既然是使用常量作為參數,那么浮點數、類對象以及字符串是不允許作為非類型模版參數的,???非類型模版參數必須在編譯期就能確認結果。

利用非類型模版參數創建一個可以自由調整大小的整形數組代碼如下:

#include <assert.h>
using namespace std;template <size_t N>
class arr
{
public:int& operator[](size_t pos){assert(pos >= 0 && pos < N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];
};
int main()
{arr<5> a1;arr<10> a2;arr<15> a3;cout <<a1.size() << endl;cout << a2.size() << endl;cout << a3.size() << endl;return 0;
}

定義一個模板類型的靜態數組

template <class T ,size_t N>
class arr
{
public:T& operator[](size_t pos){assert(pos >= 0 && pos < N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];
};
int main()
{arr<int,5> a1;arr<double,10> a2;arr<char,15> a3;cout <<a1.size()<< typeid(a1).name() << endl;cout << a2.size() << typeid(a2).name()<< endl;cout << a3.size() << typeid(a3).name()<< endl;return 0;
}

1.2注意事項

?非類型模板參數要求類型為?整型家族,其他類型是不被編譯器通過的,如果我們使用非整形的家族成員就會報錯,代碼如下:

//浮點型,非標準
template<class T, double N>
class arr 
{/*……*/ 
};

?整形家族:char? ? ?short? ? ? int? ? ? bool? ? ? ? ?long? ? ? ? ? ?longlong

1.3?實際引用

?在?C++11?標準中,引入了一個新容器?array,它就使用了?非類型模板參數,為一個真正意義上的?泛型數組,這個數組是用來對標傳統數組的。

注意:?部分老編譯器可能不支持使用此容器

新引入arry非類型模版參數的代碼?如下:

#include <iostream>
#include <cassert>
#include <array>  //引入頭文件using namespace std;int main()
{int arrOld[10] = { 0 };	//傳統數組array<int, 10> arrNew;	//新標準中的數組//與傳統數組一樣,新數組并沒有進行初始化//新數組對于越界讀、寫檢查更為嚴格 優點arrOld[15];	//老數組越界讀,未報錯arrNew[15];	//新數組則會報錯arrOld[12] = 0;	//老數組越界寫,不報錯,出現嚴重的內存問題arrNew[12] = 10;	//新數組嚴格檢查return 0;
}

array?是泛型編程思想中的產物,支持了許多?STL?容器的功能,比如?迭代器?和?運算符重載?等實用功能,最主要的改進是?嚴格檢查越界行為。

需要提醒的是:arry能做到嚴格的越界檢查 得益于 []的重載,對下標進行了嚴格檢查。

2.模版特化?

?2.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;
}

造成每次結果不一樣是因為 我們每次都日期類對象比較的時候 系統每次分配的地址都是隨機的,我們對地址進行比較是不符合實際情況的。?

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

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

?2.3類模板特化

?模板特化主要用在類模板中,它可以在泛型思想之上解決大部分特殊問題,并且類模板特化還可以分為:全特化和偏特化,適用于不同場景

后面用的日期類舉例比較多,先把日期類放出來

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

2.3.1全特化

?全特化指?將所有的模板參數特化為具體類型,將模板全特化后,調用時,會優先選擇更為匹配的模板類

//原模板
template<class T1, class T2>
class Test
{
public:Test(const T1& t1, const T2& t2):_t1(t1),_t2(t2){cout << "template<class T1, class T2>" << endl;}private:T1 _t1;T2 _t2;
};//全特化后的模板
template<>
class Test<int, char>
{
public:Test(const int& t1, const char& t2):_t1(t1), _t2(t2){cout << "template<>" << endl;}private:int _t1;char _t2;
};int main()
{Test<int, int> T1(1, 2);Test<int, char> T2(20, 'c');return 0;
}

對模板進行全特化處理后,實際調用時,會優先選擇已經特化并且類型符合的模板。

2.3.2偏特化?

?偏特化,指?將泛型范圍進一步限制,可以限制為某種類型的指針,也可以限制為具體類型

//原模板---兩個模板參數
template<class T1, class T2>
class Test
{
public:Test(){cout << "class Test" << endl;}
};//偏特化之一:限制為某種類型
template<class T1>
class Test<T1, int>
{
public:Test(){cout << "class Test<T, int>" << endl;}
};//偏特化之二:限制為不同的具體類型
template<class T>
class Test<T*, T*>
{
public:Test(){cout << "class Test<T*, T*>" << endl;}
};int main()
{Test<double, double> t1;Test<char, int> t2;Test<Date*, Date*> t3;return 0;
}

?

偏特化(尤其是限制為某種類型)在?泛型思想?和?特殊情況?之間做了折中處理,使得?限制范圍式的偏特化?也可以實現?泛型

  • 比如偏特化為?T*,那么傳?int*char*Date*?都是可行的

應用實例:

?有如下專門用來按照小于比較的類模板 :Less

#include<vector>
#include <algorithm>
template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};
int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vector<Date> v1;v1.push_back(d1);
v1.push_back(d2);v1.push_back(d3);// 可以直接排序,結果是日期升序sort(v1.begin(), v1.end(), Less<Date>());vector<Date*> v2;v2.push_back(&d1);v2.push_back(&d2);v2.push_back(&d3);// 可以直接排序,結果錯誤日期還不是升序,而v2中放的地址是升序// 此處需要在排序過程中,讓sort比較v2中存放地址指向的日期對象// 但是走Less模板,sort在排序時實際比較的是v2中指針的地址,因此無法達到預期sort(v2.begin(), v2.end(), Less<Date*>());return 0;
}
通過觀察上述程序的結果發現,對于日期對象可以直接排序,并且結果是正確的。但是如果待排序元素是指 針,結果就不一定正確。因為: sort 最終按照 Less模板中方式比較,所以只會比較指針,而不是比較指針指 向空間中內容,此時可以使用類版本特化來處理上述問題
// 對Less類模板按照指針方式特化
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* y) const{return *x < *y;}
};

?3.模版分離編譯

?

早在?模板初階?中,我們就已經知道了?模板不能進行分離編譯,會引發鏈接問題

?3.1失敗原因

?聲明與定義分離后,在進行鏈接時,無法在符號表中找到目標地址進行跳轉,因此鏈接錯誤

下面是?模板聲明與定義寫在同一個文件中時,具體的匯編代碼執行步驟

test.h?

#pragma once//聲明
template<class T>
T add(const T x, const T y);//定義
template<class T>
T add(const T x, const T y)
{return x + y;
}

?main.cpp

#include <iostream>
#include "Test.h"using namespace std;int main()
{add(1, 2);return 0;
}

?

?聲明與定義在同一個文件中時,可以直接找到函數的地址

編譯器 生成可執行文件的四個步驟:

  1. 預處理:頭文件展開、宏替換、條件編譯、刪除注釋,生成純凈的C代碼
  2. 編譯:語法 / 詞法 / 語義 分析、符號匯總,生成匯編代碼
  3. 匯編:生成符號表,生成二進制指令
  4. 鏈接:合并段表,將符號表進行合并和重定位,生成可執行程序

當模板的?聲明?與?定義?分離時,因為是?【泛型】,所以編譯器無法確定函數原型,即?無法生成函數,也就無法獲得函數地址,在符號表中進行函數鏈接時,必然失敗?

?3.2解決方案

?解決方法有兩種:

  1. 在函數定義時進行模板特化,編譯時生成地址以進行鏈接
  2. 模板的聲明和定義不要分離,直接寫在同一個文件中
//定義
//解決方法一:模板特化(不推薦,如果類型多的話,需要特化很多份)
template<>
int add(const int x, const int y)
{return x + y;
}

?

//定義
//解決方法二:聲明和定義寫在同一個文件中
template<class T>
T add(const T x, const T y)
{return x + y;
}

這也就解釋了為什么涉及?模板?的類,其中的函數聲明和定義會寫在同一個文件中 (.h),著名的?STL?庫中的代碼的聲明和定義都是在一個?.h?文件中

為了讓別人一眼就看出來頭文件中包含了?聲明?與?定義,可以將頭文件后綴改為?.hpp,著名的?Boost?庫中就有這樣的命名方式?

?

4 總結?

?

模板是 STL 的基礎支撐,假若沒有模板、沒有泛型編程思想,那么恐怕 "STL" 會變得非常大

模板的優點:

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

模板會導致代碼膨脹問題,也會導致編譯時間變長
出現模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤位置
總之,模板 是一把雙刃劍,既有優點,也有缺點,只有把它用好了,才能使代碼 更靈活、更優雅

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

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

相關文章

【C++】類和對象——構造函數和析構函數

今天要學習兩個特殊的函數&#xff0c;分別是構造函數和析構函數&#xff0c;它們究竟有什么用呢&#xff1f; 比如說&#xff0c;我們先寫一個簡單的日期的類 class Date { public:void Init() {_year 1;_month 1;_day 1;}void Print() {cout << _year << &qu…

Sentinel 分布式系統

Sentinel 是一種分布式系統的流量防衛兵和熔斷器&#xff0c;由阿里巴巴開發并開源。它的主要目標是保護分布式系統中的穩定性和可用性&#xff0c;防止因高并發或異常流量而導致的系統崩潰。下面是 Sentinel 的原理和使用教程的概要&#xff1a; Sentinel 的原理&#xff1a;…

如何去開發一個springboot starter

如何去開發一個springboot starter 我們在平時用 Java 開發的時候&#xff0c;在 pom.xml 文件中引入一個依賴就可以很方便的使用了&#xff0c;但是你們知道這是如何實現的嗎。 現在我們就來解決這一個問題&#xff01; 創建 SpringBoot 項目 首先我們要做的就是把你想要給別…

css3

基礎 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>style</title><!-- link&#xff08;外部樣式&#xff09;和style&#xff08;內部樣式&#xff09;優先級相同&#xff0c;重復寫會覆蓋 --><link re…

面試題-9

1.如何封裝一個組件 1.使用Vue.extend()創建一個組件 2.使用Vue.components()方法注冊組件 3.如果子組件需要數據,可以在props中接收定義 4.子組件修改好數據,要把數據傳遞給父組件&#xff0c;可以用emit()方法 原則: 把功能拆開 盡量讓組件原子化,一個組件做一件事情 …

centos7安裝MySQL—以MySQL5.7.30為例

centos7安裝MySQL—以MySQL5.7.30為例 本文以MySQL5.7.30為例。 官網下載 進入MySQL官網&#xff1a;https://www.mysql.com/ 點擊DOWNLOADS 點擊鏈接&#xff1b; 點擊如上鏈接&#xff1a; 選擇對應版本&#xff1a; 點擊下載。 安裝 將下載后的安裝包上傳到/usr/local下…

CTF靶場搭建及Web賽題制作與終端docker環境部署

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 寫在前面 ╔═══════════════════════════════════════════════════…

使用ChatGPT創建Makefile構建系統:使用Make運行Docker

使用ChatGPT創建Makefile構建系統&#xff1a;使用Make運行Docker 芯語芯愿&#xff08;知乎/紛傳/CSDN/&#xff09;&#xff1b;小石頭的芯語芯愿&#xff08;微信公眾號&#xff09; 開發高效現代的構建系統對于滿足開發周期需求至關重要。原先&#xff0c;嵌入式開發者一…

Unity 場景烘培 ——LensFlare鏡頭光暈(三)

提示&#xff1a;文章有錯誤的地方&#xff0c;還望諸位大神指出&#xff01; 文章目錄 前言一、鏡頭光暈 (Lens Flares)是什么&#xff1f;二、使用Lens Flares組件總結 前言 一般情況下都會忽略的東西&#xff0c;鏡頭光暈。理論上不加鏡頭光暈&#xff0c;也不會有什么影響…

vue3的兩個提示[Vue warn]: 關于組件渲染和函數外部使用

1. [Vue warn]: inject() can only be used inside setup() or functional components. 這個消息是提示我們&#xff0c;需要將引入的方法作為一個變量使用。以vue-store為例&#xff0c;如果我們按照如下的方式使用&#xff1a; import UseUserStore from ../../store/module…

數據治理之考評環節

考評的流程&#xff08;批處理&#xff09; 周期調度&#xff0c;每天一次&#xff1a;采集hive, hdfs元數據存放到mysql中的dga庫的metainfo表手動通過管理頁面補充輔助信息指標考評 讀取要考評的表的元數據及輔助信息讀取要考評的指標對每張表的每個指標逐個進行考評保存考評…

RabbitMQ快速入門(簡單收發消息)

文章目錄 前言一、數據隔離1.用戶管理2.virtual host 二、控制臺收發1.交換機2.隊列3.綁定 三、編程式收發1.依賴和配置2.收發信息 總結 前言 1.了解數據隔離 2.RabbitMQ控制臺收發信息 3.SpringBoot整合RabbitMQ收發信息 一、數據隔離 1.用戶管理 點擊Admin選項卡&#xff0…

mmdet全教程

官方給的文檔一言難盡&#xff0c;網上的教程又沒有從大綱到源碼的完整解讀&#xff0c;計劃年后開個系列記錄一下

依賴庫:Ceres-solver-2.0.0安裝

依賴庫&#xff1a;Ceres-solver-2.0.0安裝 前言安裝ceres-solver-2.0.0驗證 前言 Ceres Solver是谷歌開源的C非線性優化庫&#xff0c;能夠解決有約束或無約束條件下的非線性最小二乘問題。2010年之后大量的運用在谷歌的產品開發中&#xff0c;尤其在谷歌開源的cartographer中…

圖像分類單張圖片預測準確率達到百分之百

在圖像分類任務中&#xff0c;針對單個圖片得到100%的準確率是有可能但極其罕見的&#xff0c;并且不代表模型在整個測試集上也能達到100%的準確率。 ??針對單個圖片獲得100%準確率的情況可能包括以下幾種情形&#xff1a; 圖片本身特殊性: 如果測試集中的某張圖片在訓練集中…

【python基礎(1)】變量和簡單數據類型

文章目錄 一. 變量的命名和使用二. 字符串1. 修改字符串的大小寫2. 在字符串中使用變量3. 使用制表符或換行符來添加空白4. 刪除空白 三. 數1. 整數2. 浮點數3. 整數和浮點數4. 數中的下劃線5. 同時給多個變量賦值6. 常量 三. 注釋四. Python之禪 一. 變量的命名和使用 變量規…

各種LLM數據集包括SFT數據集

各種LLM數據集包括SFT數據集 數集介紹和 hf上的名字對話數據生成方法交通領域數據集SFT 的解釋數集介紹和 hf上的名字 通用預訓練數據集 SFT datasets SFT 數據集 50萬條中文ChatGPT指令Belle數據集:BelleGroup/train_0.5M_CN 100萬條中文ChatGPT指令Belle數據集:BelleGrou…

C++學習 --stack

目錄 1&#xff0c; 什么是stack 2&#xff0c; 創建stack 2-1&#xff0c; 標準數據類型 2-2&#xff0c; 自定義數據類型 2-3&#xff0c; 其他創建方式 3&#xff0c; 操作stack 3-1&#xff0c; 賦值 3-2&#xff0c; 插入元素(push) 3-3&#xff0c; 查詢元素 3…