網站首頁 編程語言 正文
1.快速上手
函數模板是通用的函數描述,也就是說,它們使用泛型來定義函數。
#include<iostream> using namespace std; template <typename T> void Swap(T &a,T &b);//模板原型 struct apple{ string name; double weight; int group; }; void show(apple x); int main(){ int a,b; a=1; b=2; Swap(a,b); cout<<"a:"<<a<<endl; cout<<"b:"<<b<<endl; apple c={"Alice",200,1}; apple d={"Bob",250,2}; Swap(c,d); cout<<"c:"<<endl; show(c); cout<<"d:"<<endl; show(d); } template <typename T> void Swap(T &a,T &b){ T temp; temp=a; a=b; b=temp; } void show(apple x){ cout<<"name:"<<x.name<<endl; cout<<"weight:"<<x.weight<<endl; cout<<"group:"<<x.group<<endl; }
a:2
b:1
c:
name:Bob
weight:250
group:2
d:
name:Alice
weight:200
group:1
模板函數也可以有原型:
template <typename T>
void Swap(T &a,T &b);
這里的typename
也可以換成class
。
不過模板原型實際上不常見。
模板函數定義:
template <typename T> void Swap(T &a,T &b){ T temp; temp=a; a=b; b=temp; }
模板函數隱式實例化:
Swap(a,b);
模板函數會根據實參的類型,給出函數定義。 還有顯式實例化: Swap<int>(a,b);
顯式的定義typename。 對于這兩種實例化,我推薦使用顯式實例化,因為隱式實例化容易出錯。對于這塊知識的詳細解讀,需要有對編譯器有充分的理解,在文章后面會給出。
一般我們不會用到模板函數的原型,因為我們一般把模板函數的定義放在頭文件里面,再需要使用的時候,包含頭文件就行了。
不推薦的做法:模板原型放在頭文件,模板定義放在cpp文件里。
2.重載的模板
如果對函數的重載不了解,可以翻看我之前的文章:
內聯函數、引用變量、函數重載
模板函數也可以重載,語法和常規函數的重載差不多;被重載的模板函數必須要特征標不同。
#include<iostream> using namespace std; template <typename T> void Swap(T &a,T &b);//模板原型 template <typename T> void Swap(T *a,T *b,int n);//模板原型 struct apple{ string name; double weight; int group; }; void show(apple x); int main(){ int a,b; a=1; b=2; Swap(a,b); cout<<"a:"<<a<<endl; cout<<"b:"<<b<<endl; apple c={"Alice",200,1}; apple d={"Bob",250,2}; Swap(c,d); cout<<"c:"<<endl; show(c); cout<<"d:"<<endl; show(d); char e[10]="hello"; char f[10]="bye!!"; Swap(e,f,10); cout<<"e:"<<e<<endl; cout<<"f:"<<f<<endl; } template <typename T> void Swap(T &a,T &b){ T temp; temp=a; a=b; b=temp; } template <typename T> void Swap(T *a,T *b,int n){ T temp; for(int i=0;i<n;i++){ temp=a[i]; a[i]=b[i]; b[i]=temp; } } void show(apple x){ cout<<"name:"<<x.name<<endl; cout<<"weight:"<<x.weight<<endl; cout<<"group:"<<x.group<<endl; }
a:2
b:1
c:
name:Bob
weight:250
group:2
d:
name:Alice
weight:200
group:1
e:bye!!
f:hello
3.模板的局限性
#include<iostream> using namespace std; template<class T> const T& foo(const T &a,const T &b){ if(a>b)return a; else return b; } struct apple{ string name; double weight; int group; }; void show(apple x); int main(){ apple c={"Alice",200,1}; apple d={"Bob",250,2}; apple max=foo(c,d); show(max); } void show(apple x){ cout<<"name:"<<x.name<<endl; cout<<"weight:"<<x.weight<<endl; cout<<"group:"<<x.group<<endl; }
上面這段代碼是出錯的,因為T如果是結構體,我們無法對其做>操作。當然解決這個問題的方法也是有的—顯式具體化函數。
4.顯式具體化函數
顯式具體化函數的誕生是因為模板對于某些類型的數據,定義得的函數,例如上例中得foo(c,d)
出錯,我們就單獨對這個類型,寫一個特殊的函數。
所以,就是一句話,原先模板不適用于某種類型的數據,我們就單獨給這種類型的數據,單獨來一個函數定義。
#include<iostream> using namespace std; struct apple{ string name; double weight; int group; }; template <typename T> void Swap(T &a,T &b);//模板原型 template<> void Swap<apple>(apple &a,apple &b);//顯式具體化函數原型,這里<apple>可以省略 void show(apple x); int main(){ int a,b; a=1; b=2; Swap(a,b); cout<<"a:"<<a<<endl; cout<<"b:"<<b<<endl; apple c={"Alice",200,1}; apple d={"Bob",250,2}; Swap(c,d); cout<<"c:"<<endl; show(c); cout<<"d:"<<endl; show(d); } template <typename T> void Swap(T &a,T &b){ T temp; temp=a; a=b; b=temp; } template<> void Swap<apple>(apple &a,apple &b){ cout<<"explicit specialization for apple!"<<endl; int temp; temp=a.group; a.group=b.group; b.group=temp; } void show(apple x){ cout<<"name:"<<x.name<<endl; cout<<"weight:"<<x.weight<<endl; cout<<"group:"<<x.group<<endl; }
a:2
b:1
explicit specialization for apple!
c:
name:Alice
weight:200
group:2
d:
name:Bob
weight:250
group:1
可以看出來,我們單獨為 結構體apple
搞了個顯式具體化函數,目的就是只交換group成員變量。
顯式具體化函數和常規模板很類似。
顯式具體化函數的原型:
template<>
void Swap<apple>(apple &a,apple &b);
這里<apple>
可以省略.
顯式具體化函數的定義:
template<> void Swap<apple>(apple &a,apple &b){ cout<<"explicit specialization for apple!"<<endl; int temp; temp=a.group; a.group=b.group; b.group=temp; }
實際上這段代碼也意味著,顯式具體化的優先級高于常規模板。
5.實例化和具體化
切記!函數模板本身不會生成函數定義,它只是一個生成函數定義的方案!
編譯器使用模板為特定類型生成函數定義時,得到的是模板實例。生成函數定義就是實例化。
實例化有隱式和顯式之分。
隱式實例化:
Swap(a,b);
或者Swap<int>(a,b);
隱式實例化是指等你調用了這個函數的時候,它才會生成函數定義。
顯式實例化:
template void Swap<int>(int,int);
顯式實例化是指不需要等你調用這個函數,使用上面那段代碼,直接能生成Swap<int>
函數的定義。 一般來說,我們會把模板放到一個頭文件中,然后很多源文件會include它,然后編譯的時候就會在這些源文件中生成具體化的代碼。但是如果我們采用顯式實例化,在其中一個源文件里面實例化一份代碼,然后其他cpp文件用到的時候,通過鏈接程序找到這個代碼并調用它,程序的大小就會少一些。這就是顯式實例化的好處。
下面這段代碼展示了Add<double>(a,b)
相較于Add(a,b)
的優越性:
#include<iostream> using namespace std; template <typename T> T Add(const T &a,const T &b){ return (a+b); } int main(){ int a=5; double b=6.1; cout<<Add<double>(a,b)<<endl; }
如果把Add<double>(a,b)
換成Add(a,b)
會出錯,因為a是int類型的,而b是double類型的,這樣就無法隱式實例化了。Add<double>(a,b)
會實例化一個函數定義,然后int類型的a,傳參給double的引用形參的時候,會產生臨時變量,從而完成函數調用。總之,最好使用<type>
而不是根據參數類型自動生成模板的實例化.
顯式隱式實例化和顯式具體化統稱為具體化或者實例化
上一節中我們提到了顯式具體化,我們可以發現實例化和顯式具體化的相同之處在于,他們都是使用具體類型的函數定義,而不是通用描述。
顯式具體化函數是否是模板? 我的回答是:顯式具體化函數是一個特殊的模板,它是專門為一種類型設計的模板。
//函數模板6.cpp #include<iostream> using namespace std; struct apple{ string name; double weight; int group; }; template<class T> void Swap(T &a,T &b);//模板函數原型 template<>void Swap(apple &a,apple &b);//顯式具體化原型 template void Swap<char>(char&,char&);//顯式實例化 void show(apple x); int main(){ short a=1; short b=2; Swap(a,b);//隱式實例化 cout<<"a:"<<a<<endl<<"b:"<<b<<endl; apple c={"Alice",200,1}; apple d={"Bob",250,2}; Swap(c,d);//顯式具體化 cout<<"c:"<<endl; show(c); cout<<"d:"<<endl; show(d); char e='a'; char f='b'; Swap<char>(e,f);//調用顯式實例化函數 cout<<"e:"<<e<<endl<<"f:"<<f<<endl; } template<> void Swap(apple &a,apple &b){ int temp; temp=a.group; a.group=b.group; b.group=temp; } void show(apple x){ cout<<"name:"<<x.name<<endl; cout<<"weight:"<<x.weight<<endl; cout<<"group:"<<x.group<<endl; } template<class T> void Swap(T &a,T &b){ T temp; temp=a; a=b; b=temp; }
a:2 ? ? ??
b:1 ? ? ??
c: ? ? ? ?
name:Alice
weight:200
group:2 ??
d: ? ? ? ?
name:Bob ?
weight:250
group:1
e:2.01
f:1?
這里問個問題,如果把上面代碼中的e變成 int類型會出現問題嗎?
會報錯,因為實參int
和函數中引用形參char&
的類型不一樣,且此時不是const引用形參,也不會有臨時變量產生。如果你不清楚,且看引用變量的語法。 內聯函數、引用變量、函數重載
6.重載解析
6.1 概覽
對于常規函數,函數重載,函數模板,函數模板重載,編譯器需要有一個良好的策略,從一大堆同名函數中選擇一個最佳函數定義。這一過程是非常復雜的過程–重載解析。這就是我們這一節要闡述的內容。
重載解析過程:
- step1:創建候選函數列表。其中包含與被調用函數名稱相同的函數和模板函數。
- step2:從候選函數列表中篩選可行函數。其中包括參數正確或者隱式轉換后參數正確的函數。
- step3:確定是否存在最佳的可行函數。如果有則使用他,否則函數調用出錯。
其中最復雜的就是step3,這些可行函數也有優先級之分,優先級 從高到低是:
- 完全匹配
- 提升轉化 (如,char short 轉化成int,float 轉化成 double)
- 標準轉化 (如,int 轉化成 char ,long轉化成double)
- 用戶定義的轉化 (如類聲明中定義的轉換)
而完全匹配中也有細小的優先級之分。
總而言之,在step3
中如果優先級最高的可行函數是唯一的那么就調用他,否則會出現諸如ambiguous
的錯誤。
這一節的目的就是完全理解編譯器如何讓處理如下代碼:
#include<iostream> using namespace std; void may(int);//#1 float may(float,float=3);//#2存在默認參數 void may(char &);//#3 char* may(const char*);//#4 char may(const char &);//#5 template<class T> void may(const T &);//#6 template<class T> void may(T *);//#7 int main(){ may('B'); } void may(int a){ cout<<1<<endl; } float may(float a,float b){ cout<<2<<endl; return a; } void may(char &a){ cout<<3<<endl; } char* may(const char* a){ cout<<4<<endl; return NULL; } char may(const char &a){ cout<<5<<endl; return a; } template<class T> void may(const T & a){ cout<<6<<endl; } template<class T> void may(T *){ cout<<7<<endl; }
上述代碼沒有一點問題,甚至連warning都沒有,你可以自己試一下結果是什么。
'B'
是const char類型的
#1~#7都是候選函數,因為函數名字相同。
其中#1、#2、#3、#5、#6是可行函數,因為const char 類型無法隱式轉換成指針類型,所以#4、#7不行,而其他函數通過隱式轉換后參數是正確的。
#1是提升轉換,#2是標準轉換,#3、#5、#6是完全匹配,完全匹配中非模板函數比模板函數優先級高,所以#3、#5優先級高于#6,而由于const參數優先和const引用參數匹配,所以#5的優先級更高。
則#5>#3>#6>#1>#2,所以調用#5。
6.2 完全匹配中的三六九等
首先什么是完全匹配?
完全匹配函數包括:
- 不需要進行隱式類型轉化的函數(即參數正確的函數)顯然是完全匹配函數。
- 需要進行隱式類型轉換,但是這些轉換是無關緊要轉換。
完全匹配允許的無關緊要轉換:
實 參 | 形 參 |
---|---|
Type | Type& |
Typc& | Type |
Type[] | * Type |
Type (argument-list) | Type ( * ) (argument-list) |
Type | const Type |
Type | volatile Type |
Type * | const Type |
Type* | volatile Type * |
完全匹配中的優先級法則
- 常規函數優先級高于模板。
- 對于形參是指針或引用類型的函數,const修飾的實參優先匹配const修飾的形參,非const修飾的實參優先匹配非const修飾的形參。
- 較具體的模板優先級高于較簡略的模板。(例如,顯式具體化函數優先級高于常規模板)
#include<iostream> using namespace std; struct apple{ string name; double weight; int group; }; void may(const apple & a){ cout<<1<<endl; } void may(apple &a){ cout<<2<<endl; } int main(){ apple a={"Alice",250.00,1}; may(a); }
結果是2
#include<iostream> using namespace std; struct apple{ string name; double weight; int group; }; void may(const apple & a){ cout<<1<<endl; } void may(apple &a){ cout<<2<<endl; } void may(apple a){ cout<<3<<endl; } int main(){ apple a={"Alice",250.00,1}; may(a); }
這個編譯器會出錯,因為這三個函數都是完全匹配,但是#2 和 #3的優先級無法區別,記得嗎,完全匹配中的優先級法則的第2條法則,只適用于形參是引用或者指針。
#include<iostream> using namespace std; struct apple{ string name; double weight; int group; }; template<typename T> void may(T a){ cout<<1<<endl; } template<typename T> void may(T *a){ cout<<2<<endl; } int main(){ apple a={"Alice",250.00,1}; may(&a); }
終端輸出是2,&a
的類型是 apple*
,而#2明確指出形參是個指針,所以#2更具體。
關于如何找出最具體的模板的規則被稱為部分排序規則。
部分排序規則:在實例化過程中,函數優先和轉換少的模板匹配。也可以這么說,實參和形參越相似,模板越優先。
舉個栗子:
#include<iostream> using namespace std; template<typename T> void may(T a[]){ cout<<1<<endl; } template<typename T> void may(T *a[]){ cout<<2<<endl; } template<typename T> void may(const T *a[]){ cout<<3<<endl; } int main(){ double a[5]={1,2,3,4,5}; const double* b[5]={&a[0],&a[1],&a[2],&a[3],&a[4]}; may(a); may(b); }
may(a)
會和#1匹配,因為a的類型是double數組,double數組無法轉換成指針數組,所以#2,#3不是可行函數。而對于may(b)
,他會和#3匹配。b的類型是cont指針數組,首先#1和#2和#3都是可行函數,而且都是完全匹配函數,因為#1 會實例化成may<const double*>(b)
,#2 他實例化成may<const double>(b)
,#3會實例化為may<double>(b)
所以我們看看那個模板更具體?#3模板直接指出了 形參是一個const指針數組,所以他最具體,#3優先級最高;其次是#2因為它的形參指出了是指針數組;#1是最不具體的,#3>#2>#1.
6.3 總結
可行函數中優先級從高到低排列 | ? | ? |
---|---|---|
完全匹配 | 常規函數 | 形參若是指針或引用,注意const和非const |
? | 模板 | 較具體的模板優先級更高 |
提升轉換 | ? | ? |
標準轉換 | ? | ? |
用戶定義轉換 | ? | ? |
Swap<>(a,b)
這種代碼,類似于顯式實例化,但是<>中沒有指出typename,所以這段代碼是要求優先選擇模板函數。
對于多參數的函數,優先級會非常復雜,就不談了。
7.模板的發展
關鍵字decltype 和 auto
#include<iostream>using namespace std;template<typename T1,typename T2>auto Add(T1 a, T2 b){ decltype(a+b) c; c=a+b; return c;}int main(){ int a=2; double b=2.123; cout<<Add(a,b);}#include<iostream> using namespace std; template<typename T1,typename T2> auto Add(T1 a, T2 b){ decltype(a+b) c; c=a+b; return c; } int main(){ int a=2; double b=2.123; cout<<Add(a,b); }
關鍵字decltype 和 auto ,在模板中無法確定數據類型時,發揮了巨大的作用。
原文鏈接:https://blog.csdn.net/m0_71009069/article/details/126267585
相關推薦
- 2023-06-18 C#?指針內存控制Marshal內存數據存儲原理分析_C#教程
- 2022-04-11 C++?std::initializer_list?實現原理解析及遇到問題_C 語言
- 2022-11-05 React+CSS?實現繪制豎狀柱狀圖_React
- 2022-05-16 C語言中有哪些字符處理函數你知道嗎_C 語言
- 2022-09-05 Spring是如何解決循環依賴的?
- 2023-03-01 PostgreSQL生成列實現過程介紹_PostgreSQL
- 2022-05-27 C++左值與右值,右值引用,移動語義與完美轉發詳解_C 語言
- 2022-05-24 Golang?錯誤捕獲Panic與Recover的使用_Golang
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支