C++ 模版進階

目錄

前言

1. 非類型模版參數

1.1 概念與講解

1.2? array容器

2. 模版的特化

2.1 概念

2.2 函數模版特化

2.3 類模版特化

2.3.1 全特化

2.3.2 偏特化

3.模版的編譯分離

3.1 什么是分離編譯

3.2 模版的分離編譯

3.3 解決方法

4. 模版總結

總結


前言

本篇文章主要講解的是模版進階的內容,其中有模版更深入的應用,內容豐富,干貨多多!


1. 非類型模版參數

1.1 概念與講解

模版參數分類類型形參非類型形參

類型形參即,出現在模版參數列表中,跟在class或者typename后面的參數類型名稱。

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

我們來看下面的場景。

  • 我們要完成一個靜態的棧,一般可以使用宏來定義N,需要存儲多少數據,修改宏的大小即可。如果在main函數中我們想讓第一個棧存儲10個數據,第二個棧存儲100數據。
  • 此時,宏的弊端就體現出來。因為N只能表示一個數值,為了滿足上面的要求,將N定義為100。可是第一個棧只需要存儲10個數據,這樣就會造成空間上的浪費。
#define N 100//靜態的棧
template<class T>
class Stack
{
private:T a[N];int top;
};int main()
{Stack<int> st1;  //10Stack<int> st2;  //100return 0;
}

為了解決上面出現的問題,我們可以使用非類型模版參數。

//靜態的棧
template<class T, size_t N>
class Stack
{
private:T a[N];int top;
};int main()
{Stack<int, 10> st1;  //10Stack<int, 100> st2;  //100return 0;
}

  • 非類型模版參數還可以給缺省值,類似于函數參數。
  • 非類型模版參數是個常量,不可以修改。
//靜態的棧
template<class T, size_t N = 10>
class Stack
{
public:void func(){N++;//不可以修改N}private:T a[N];int top;
};int main()
{Stack<int> st1;  //10Stack<int, 100> st2;  //100st1.func();//會報錯return 0;
}

  • C++20之前的版本只允許整型類型做非類型模版參數。
  • C++20之后的版本支持所有內置類型做非類型模版參數,但是不支持自定義類型參數做非類型模版參數
template<double X, string str>
class Unkonwn
{};int main()
{Unkonwn<1.1, "xxxxx"> un;return 0;
}

1.2? array容器

非類型模版參數既然可以解決一些場景下的問題,在STL中的容器有沒有使用的呢?array容器就是用非類型模版參數。相當于一個定長數組,進行了封裝。

#include <array>
int main()
{array<int, 10> aa1;cout << sizeof(aa1) << endl;return 0;
}

相比于之前的數組,array容器有什么優勢呢?

  • 之前的定長數組,檢查越界方面使用的是抽查機制,即檢查超出數組下標一兩位內存空間是否發生改變,如果超出數組下標太多,可能檢查不到,并且檢查的成本很大。只有你進行寫的操作可以檢查出來,如果進行讀操作,打印出來時,無法檢查出來。
  • array容器是自定義類型,下標方括號訪問是運算符重載,可以對方括號內的參數進行限制,不管是寫還是讀操作,都能檢查出來。
#include <array>
int main()
{//嚴格的越界檢查array<int, 10> aa1;aa1[10] = 10;cout << aa1[11] << endl;int aa2[10];aa2[10] = 10; //大多數編譯器檢查的出來aa2[14] = 10; //一般的編譯器檢查不出來,除非是新版的cout << aa2[15] << endl; //檢查不出來return 0;
}

2. 模版的特化

2.1 概念

通常情況下,使用模版可以實現一些與類型無關的代碼,但對于一些特殊類型的肯呢個會得到一些錯誤的結果,需要進行特殊處理。如下面的場景,寫一個用來進行小于比較的函數模版。

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{if (_year < d._year)return true;elseif (_year == d._year)if (_month < d._month)return true;elseif (_month == d._month)return _day < d._day;return false;}
private:int _year;int _month;int _day;
};// 函數模板 -- 參數匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{cout << Less(1, 2) << endl;// 可以比較Date d1(2024, 7, 8);Date d2(2024, 7, 5);cout << Less(d1, d2) << endl; // 可以比較Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比較,但是結果錯誤return 0;
}

運行結果如下:

如上述代碼所示,Less函數使用模版后對內置類型和自定義類型都可以進行比較操作,但是Date*日期指針類型返回的結果是跟Date類型結果不同,因為p1表示d1日期類變量的地址,p2表示d2日期類變量的地址,只有對p1和p2進行解引用時,才是指向d1和d2兩個變量的內容,所以指針相關類型比較特殊。

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

2.2 函數模版特化

函數模版特化要求:

  1. 必須要先有一個基礎的函數模版。
  2. 關鍵字template后面接一對空的尖括號<>。
  3. 函數名后跟一對尖括號,尖括號中指定需要特化的類型。
  4. 函數形參必須要和原模版函數的基礎參數類型完全相同,需要注意的是,如果不同編譯器可能會報一些奇怪的錯誤

一開始用Less函數舉例子,指出對于指針類型無法比較,我們可以使用函數模版來解決。

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(2024, 7, 8);Date d2(2024, 7, 5);cout << Less(d1, d2) << endl; Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; //調用特化函數模版,進行比較return 0;
}

運行結果如下:

不過使用模版函數會有許多坑。我們一般對函數使用模版時,函數參數會對模版使用引用,如果這個函數不進行修改操作,會在前面加上const修飾,防止該變量被修改。

  • 如下面代碼所示,一般人寫函數模版的特化,會寫成第一種。
  • 這是錯誤的,因為T此時是Date*指針類型,const T& left中的const修飾的是left,且left本身存儲的是變量的地址,所以是指針變量本身不能改變。如果寫成第一種const加在Date*前,修飾的是指針指向的內容,跟原函數模版的函數參數類型不同,編譯時會報錯。
  • 第二種函數模版的特化才是正確的寫法,const放在*符號之后,表示修飾left存儲變量的地址。
// 函數模板 -- 參數匹配
template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}//1.
template<>
bool Less<Date*>(const Date* & left, const Date* & right)
{return *left < *right;
}//2.
template<>
bool Less<Date*>(Date* const & left, Date* const & right)
{return *left < *right;
}

2.3 類模版特化

類模版的特化分為全特化偏特化

2.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; }
};void Test1()
{Data<int, double> d1; //走第一個構造函數Data<int, char> d2; //走特化的構造函數
}

運行結果如下:

2.3.2 偏特化

偏特化:任何針對模版參數進一步進行條件限制設計的特化版本。

  • 部分特化:將模版參數表中的一部分參數特化。
//特化第二個參數int
template<class T1>
class Data<T1, int>
{
public:Data(){cout << "Data<T1, int> -偏特化" << endl;}
};

  • 進一步限制參數類型。偏特化并不僅僅是指特化部分參數,而是針對模版參數更進一步的條件限制所設計出來的一個特化版本。
// 限定模版類型
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:Data(){cout << "Data<T1*, T2*> -偏特化" << endl;}
};void Test2()
{Data<int, double> d1;Data<int, char> d2;Data<int, int> d3;Data<int*, double*> d4;Data<int**, char*> d5;
}

運行結果如下:

再看下面的代碼,先對第二種偏特化的構造函數做一些修改,其中typeid(T1).name( )是獲取T1的類型名稱,下面一條也是。

// 限定模版類型
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:Data(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;cout << "Data<T1*, T2*> -偏特化" << endl;}
};void Test3()
{Data<int*, double*> d4;Data<int**, char*> d5;
}

運行結果如下,T1和T2分表是原來*符號前的類型,而不是原來的指針類型。

3.模版的編譯分離

3.1 什么是分離編譯

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

3.2 模版的分離編譯

下面的代碼是模版函數和普通函數分離編譯的代碼,看看運行的結果如何。建立兩個文件,分別是Func.h和Func.cpp。

  • Func.h文件存放函數的聲明。
//Func.h//模版函數
template<class T>
T Add(const T& left, const T& right);//普通函數
void func();
  • Func.cpp文件存放函數的定義。
//Func.cpp
#include "Func.h"
//模版函數
template<class T>
T Add(const T& left, const T& right)
{cout << "T Add(const T& left, const T& right)" << endl;return left + right;
}//普通函數
void func()
{cout << "void func()" << endl;
}

寫兩個測試函數,運行程序看結果。

//Test.cpp
#include "Func.h"
void test1()
{Add(1, 2);     Add(1.0, 2.0); 
}void test2()
{func();
}int main()
{test1();test2();return 0;
}

運行test1函數,報連接錯誤,說有無法解析的外部符號。

運行test2函數,結果如下,沒有問題。

普通函數分離編譯可以正常運行,模版函數分離編譯后運行會報鏈接錯誤,這是為什么呢?

  • C/C++程序要運行起來,都要經過這幾個步驟:預處理—>編譯—>匯編—>鏈接
  • 在預處理階段,所有的.h文件都要在包含的位置展開。在編譯過程中,將Func.cpp和Test.cpp文件編譯成目標文件,分別是Func.o和Test.o。
  • 并且會生成一個符號表,符號表中有函數名經過不同平臺規則的變化成新的名稱,并且會在函數的定義中,存放函數地址,方便鏈接。
  • 但是模版函數在定義的部分,不知道要使用什么類型實例化,就不會將Add函數的地址存放在符號表中,那么就會出現上面報的連接錯誤,無法解析的符號。

?

?

3.3 解決方法

有兩種解決方法:

  1. 將聲明和定義放在同一個.hpp或者.h文件中
  2. 在模版函數定義的位置顯示實例化。

如下面的代碼所示,不過這種方法很少用。因為當你使用到這個函數其他類型的話,還需要再添加,十分麻煩。

#include "Func.h"template<class T>
T Add(const T& left, const T& right)
{cout << "T Add(const T& left, const T& right)" << endl;return left + right;
}//顯示實例化
template
int Add(const int& left, const int& right);template
double Add(const double& left, const double& right);

4. 模版總結

模版的優點:

  1. 模版復用代碼,節省資源,提高開發效率。
  2. 增強代碼的靈活性。

缺點:

  1. 模版會導致代碼膨脹問題,使得編譯時間變長。
  2. 出現模版編譯錯誤時,錯誤信息非常凌亂,難以定位錯誤進行糾正。


總結

通過這篇文章,對于模版的使用有了更深入的了解,如果還有某些地方不夠熟悉,可以自己動手敲敲代碼。

創作不易,希望這篇文章能給你帶來啟發和幫助,如果喜歡這篇文章,請留下你的三連,你的支持的我最大的動力!!!

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

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

相關文章

包/final/權限修飾符/代碼塊

包package 1、包的作用 包用來管理不同的類。 2、包名 包名要全部小寫&#xff0c;一般是域名反寫&#xff0c;如com.liu。在Java中&#xff0c;java解釋器會將package中的.解釋為目錄分隔符/&#xff0c;也就是說該文件的目錄結構為&#xff1a;...com/liu/... 3、全類名…

1.pwn的匯編基礎(提及第一個溢出:整數溢出)

匯編掌握程度 能看懂就行&#xff0c;絕大多數情況不需要真正的編程(shellcode題除外) 其實有時候也不需要讀匯編&#xff0c;ida F5 通常都是分析gadget&#xff0c;知道怎么用&#xff0c; 調試程序也不需要分析每一條匯編指令&#xff0c;單步執行然后查看寄存器狀態即可 但…

Text2SQL提問中包括時間的實戰方案

大家好,我是herosunly。985院校碩士畢業,現擔任算法研究員一職,熱衷于機器學習算法研究與應用。曾獲得阿里云天池比賽第一名,CCF比賽第二名,科大訊飛比賽第三名。擁有多項發明專利。對機器學習和深度學習擁有自己獨到的見解。曾經輔導過若干個非計算機專業的學生進入到算法…

實現多數相加,但是傳的參不固定

一、情景 一般實現的加法和減法等簡單的相加減函數的話。一般都是寫好固定傳的參數。比如&#xff1a; function add(a,b) {return a b;} 這是固定的傳入倆個&#xff0c;如果是三個呢&#xff0c;有人說當然好辦&#xff01; 這樣寫不就行了&#xff01; function add(a…

vue中自定義設置多語言(包括使用vue-i18n),并且運行js腳本自動生成多語言文件

在項目中需要進行多個國家語言的切換時&#xff0c;可以用到下面方法其中一個 一、自定義設置多語言 方法一: 可以自己編寫一個設置多語言文件 在項目新建js文件&#xff0c;命名為&#xff1a;language.js&#xff0c;代碼如下 // language.js 文檔 let languagePage {CN…

聊一下Maven打包的問題(jar要發布)

文章目錄 一、問題和現象二、解決方法&#xff08;1&#xff09;方法一、maven-jar-pluginmaven-dependency-plugin&#xff08;2&#xff09;方法二、maven-assembly-plugin 一、問題和現象 現在的開發一直都是用spring boot&#xff0c;突然有一天&#xff0c;要自己開發一個…

Django之項目開發(二)

目錄 一、安裝和使用uWSGI 1.1、安裝 1.2、配置文件 1.3、啟動與停止uwsgi 二、安裝nginx 三、Nginx 配置uWSGI 四、Nginx配置靜態文件 五、Nginx配置負載均衡 一、安裝和使用uWSGI uWSGI 是一個 Web 服務器,可以用來部署 Python Web 應用。它是一個高性能的通用的 We…

味蕾與理解:應對自閉癥兒童挑食的策略與理解

在星貝育園自閉癥康復學校&#xff0c;我們深知飲食習慣對孩子們的成長至關重要&#xff0c;而自閉癥兒童的挑食問題往往比同齡兒童更為突出&#xff0c;給家長和照顧者帶來了額外的挑戰。今天&#xff0c;作為這里的老師&#xff0c;我想與大家分享一些應對自閉癥兒童挑食的策…

(南京觀海微電子)——電阻應用及選取

什么是電阻&#xff1f; 電阻是描述導體導電性能的物理量&#xff0c;用R表示。 電阻由導體兩端的電壓U與通過導體的電流I的比值來定義&#xff0c;即&#xff1a; 所以&#xff0c;當導體兩端的電壓一定時&#xff0c;電阻愈大&#xff0c;通過的電流就愈小&#xff1b;反之&…

鴻蒙應用實踐:利用扣子API開發起床文案生成器

前言 扣子是一個新一代 AI 應用開發平臺&#xff0c;無需編程基礎即可快速搭建基于大模型的 Bot&#xff0c;并發布到各個渠道。平臺優勢包括無限拓展的能力集&#xff08;內置和自定義插件&#xff09;、豐富的數據源&#xff08;支持多種數據格式和上傳方式&#xff09;、持…

[Unity入門01] Unity基本操作

參考的傅老師的教程學了一下Unity的基礎操作&#xff1a; [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移動&#xff1a;鼠標中鍵旋轉&#xff1a;鼠標右鍵放大&#xff1a;鼠標滾輪飛行模式&#xff1a;右鍵WASDQEFocus模式&…

算法設計與分析 實驗5 并查集法求圖論橋問題

目錄 一、實驗目的 二、問題描述 三、實驗要求 四、實驗內容 &#xff08;一&#xff09;基準算法 &#xff08;二&#xff09;高效算法 五、實驗結論 一、實驗目的 1. 掌握圖的連通性。 2. 掌握并查集的基本原理和應用。 二、問題描述 在圖論中&#xff0c;一條邊被稱…

基于Android Studio訂餐管理項目

目錄 項目介紹 圖片展示 運行環境 獲取方式 項目介紹 能夠實現登錄&#xff0c;注冊、首頁、訂餐、購物車&#xff0c;我的。 用戶注冊后&#xff0c;登陸客戶端即可完成訂餐、瀏覽菜譜等功能&#xff0c;點餐&#xff0c;加入購物車&#xff0c;結算&#xff0c;以及刪減…

【學習筆記】操作系統--萬字長文

計算機操作系統 文章目錄 計算機操作系統引言 操作系統基本概念第一章 引論目標和作用操作系統發展歷程單道批處理系統多道批處理系統分時系統實時系統 基本特征并發共享虛擬異步性&#xff08;不確定性&#xff09; 操作系統主要功能處理機管理內存管理設備管理文件管理 第二章…

python `queue` 模塊提供了同步的、線程安全的隊列類

在Python中&#xff0c;queue 模塊提供了同步的、線程安全的隊列類&#xff0c;這使得在多線程環境下共享數據變得簡單。下面是一個使用 queue.Queue 的并發編程示例&#xff0c;其中使用了 threading 模塊來創建多個線程&#xff0c;這些線程將向隊列中添加元素并從隊列中取出…

探索 WebKit 的前沿之旅:HTML5 新特性的卓越處理

探索 WebKit 的前沿之旅&#xff1a;HTML5 新特性的卓越處理 隨著 Web 技術的飛速發展&#xff0c;HTML5 已經成為構建現代網頁和應用的基石。WebKit&#xff0c;作為領先的瀏覽器引擎之一&#xff0c;承載著將這些創新技術轉化為用戶可感知體驗的使命。本文將深入探討 WebKit…

工程化:Commitlint / 規范化Git提交消息格式

一、理解Commitlint Commitlint是一個用于規范化Git提交消息格式的工具。它基于Node.js&#xff0c;通過一系列的規則來檢查Git提交信息的格式&#xff0c;確保它們遵循預定義的標準。 1.1、Commitlint的核心功能 代碼規則檢查&#xff1a;Commitlint基于代碼規則進行檢查&a…

匯聚榮拼多多電商的技巧有哪些?

在電商平臺上&#xff0c;匯聚榮拼多多以其獨特的商業模式和創新的營銷策略吸引了大量消費者。那么&#xff0c;如何在這樣一個競爭激烈的平臺上脫穎而出&#xff0c;成為銷售佼佼者呢?本文將深入探討匯聚榮拼多多電商的成功技巧。 一、精準定位目標客戶群體 首先&#xff0c;…

Python魔法函數(Magic Methods簡介

在 Python 中&#xff0c;魔法函數&#xff08;Magic Methods&#xff09;也稱為雙下劃線方法&#xff08;Dunder Methods&#xff09;&#xff0c;是指那些名字以雙下劃線開頭和結尾的特殊方法。 這些方法可以讓您的自定義類實現一些特定的行為&#xff0c;從而與 Python 的內…

絕區肆--2024 年AI安全狀況

前言 隨著人工智能系統變得越來越強大和普及&#xff0c;與之相關的安全問題也越來越多。讓我們來看看 2024 年人工智能安全的現狀——評估威脅、分析漏洞、審查有前景的防御策略&#xff0c;并推測這一關鍵領域的未來可能如何。 主要的人工智能安全威脅 人工智能系統和應用程…