C++模板非類型形參的詳細講解
前言
關(guān)于模板的非類型形參,網(wǎng)上有很多內(nèi)容,C++primer只有大概一頁的闡述,但是都不夠清晰詳細。下面我盡可能從自己的角度去給大家描述一下非類型形參的相關(guān)細節(jié)。如果想進一步理解非類型形參以及模板內(nèi)容可以閱讀C++template這本書,在4.1節(jié),8.3.3節(jié),13.2節(jié)都有相關(guān)解釋。
模板除了定義類型參數(shù),我們還可以在模板定義非類型參數(shù)。
什么是非類型形參?顧名思義,就是表示一個固定類型的常量而不是一個類型。
先舉一個簡單的例子(模板類與模板函數(shù)都可以用非類型形參)
//例子1: template<class T,int MAXSIZE> class List{ private: T elems[MAXSIZE]; public: Print(){ cout<<"The maxsize of list is"<<MAXSIZE; } } List<int,5> list; list.Print();//打印"The maxsize of list is 5"
這個固定類型是有局限的,只有整形,指針和引用才能作為非類型形參,而且綁定到該形參的實參必須是常量表達式,即編譯期就能確認結(jié)果。
這里要強調(diào)一點,我們對于非類型形參的限定要分兩個方面看
1.對模板形參的限定,即template<>里面的參數(shù)
2.對模板實參的限定,即實例化時<>里面的參數(shù)
下面逐個解釋一下非類型形參的局限
1.浮點數(shù)不可以作為非類型形參,包括float,double。具體原因可能是歷史因素,也許未來C++會支持浮點數(shù)。
2.類不可以作為非類型形參。
3.字符串不可以作為非類型形參
4.整形,可轉(zhuǎn)化為整形的類型都可以作為形參,比如int,char,long,unsigned,bool,short(enum聲明的內(nèi)部數(shù)據(jù)可以作為實參傳遞給int,但是一般不能當(dāng)形參)
5.指向?qū)ο蠡蚝瘮?shù)的指針與引用(左值引用)可以作為形參
下面解釋一下非類型實參的局限
1.實參必須是編譯時常量表達式,不能使用非const的局部變量,局部對象地址及動態(tài)對象
2.非Const的全局指針,全局對象,全局變量(下面可能有個特例)都不是常量表達式。
3.由于形參的已經(jīng)做了限定,字符串,浮點型即使是常量表達式也不可以作為非類型實參
備注:常量表達式基本上是字面值以及const修飾的變量
//例子2: template<class T,int MAXSIZE> class List{ private: T elems[MAXSIZE]; public: void Print(){ cout<<"The maxsize of list is "<<MAXSIZE; } }; const int num1 = 9; ;//全局變量 static int num2= 9; ;//全局變量 const int num3 = 9; ;//局部變量 List<int,num1> list; //正確 List<int,num2> list; //錯誤 List<int,num3> list; //正確 //再看一個關(guān)于指針和字符串比較特別的例子 //例子3: template<char const* name> class pointerT{ }; char a[] = "saaa";;//全局變量 char a2[] = "saaa";;//局部變量,寫在main函數(shù)里面 char *b = "saaa";//全局變量 char *const c = "saaa";//全局變量,頂層指針,指針常量 pointerT<"testVarChar"> p1;//錯誤 pointerT<a> p2;//正確 pointerT<a2> p22;//錯誤,局部變量不能用作非類型參數(shù) pointerT<b> p3;//錯誤,error C2975:“pointerT”的模板參數(shù)無效,應(yīng)為編譯時常量表達式 pointerT<c> p4;//錯誤,error C2970: “c”: 涉及帶有內(nèi)部鏈接的對象的表達式不能用作非類型參數(shù)
//關(guān)于指針常量和常量指針可以參考博客
Const用法總結(jié)(快速區(qū)分指針常量與常量指針)
這里大家可能會有幾個疑問
①.到底為什么字符串不能作為實參?
答:我們看到上面p1的模板實參是"testVarChar",然而當(dāng)我們在另一個編譯單元(.cpp文件)同樣聲明這么一個模板實例時,這兩個"testVarChar"的地址可能是不同的,編譯器傳遞給模板時就會傳遞傳遞不同的地址,從而導(dǎo)致這兩個模板實例是兩個不同且不兼容的類型。這就是支持字符串的問題所在。(這里可能更深的涉及模板的實現(xiàn)原理)
②.變量b和c作為模板實參為什么錯誤不同?
答:首先解釋b實參,b在這里看做是一個指針,是一個全局指針,但是他不是一個常量表達式,所以b不對。我們再看看c,c相比于b對了一個const修飾符,表示這個指針是一個常量。然而const是一個比較特別的關(guān)鍵字,他具有內(nèi)部鏈接屬性(關(guān)于內(nèi)連接參考博客 理解C++的鏈接:C++內(nèi)鏈接與外鏈接的意義),也就是說僅在定義這個變量的文件內(nèi)可見,不會造成不同編譯單元的混編時的鏈接錯誤。
這個特性對于模板來說可是有問題的,就像問題①所描述的,由于每個編譯單元可能都有一個c變量,導(dǎo)致在編譯時,實例化多個c,而且c的地址還不同,這就造成二個模板的實例是兩個不同且不兼容的類型。
③為什么a變量作為實參可以?
答:我看過一些書籍,上面舉得例子都是用const修飾不行的情況下在加extern來形成extern constchara[]="saaa";這樣形式的語句,extern和const聯(lián)合使用確實可以壓制const的內(nèi)部屬性。
這個a這里可以看做一個數(shù)組類型,進一步理解數(shù)組與指針的關(guān)系
附:char * itoa(int, char *, int); 第二個參數(shù)明明是char*,為什么卻又不能是“char*”?
Itoa這個函數(shù)大家應(yīng)該多多少少接觸過,它的功能使把一個整型按照你給的進制轉(zhuǎn)換成你想要的字符串,也就是這個函數(shù)讓我覺得有必要再去研究一下字符串?dāng)?shù)組和字符串指針的區(qū)別。
首先看itoa這個函數(shù)原型,char * itoa(int originNum, char * targetStr, int standard);
第一個參數(shù)你的整型數(shù)據(jù),第二個是一個字符串,第三個是一個int型表示N進制。
現(xiàn)在我們測試一下,
char *str=“hello”;
int num=123;
_itoa_s(num, str,10); //vs C++下使用會提示編譯錯誤
itoa(num,str,10);//codeblocks下運行會崩潰,正常環(huán)境下都會崩潰的
char str2[]=“hello”;
itoa(num, str2,10);//運行正常
這樣我們發(fā)現(xiàn)明明函數(shù)原型的參數(shù)就是char*,為什么我們寫的str卻不行呢?
char *和char[]到底有什么區(qū)別?
這里,我們先從其本質(zhì)說起。說到底一個是數(shù)組一個是指針,兩者其實除了都能保存字符串外區(qū)別確實大了。最重要的一點區(qū)別就是內(nèi)存分配(關(guān)于C語言變量在內(nèi)存的存儲位置,大家可以參考…….),對于基本類型的單個變量與數(shù)組我們都會為其在棧上申請空間來存放數(shù)據(jù),而指針只是指向一塊內(nèi)存的索引,所以char*聲明的只是指向常量區(qū)”hello”的指針。
這時候我們再看一下itoa的功能,它是要把num轉(zhuǎn)換成字符串存在str里面,然而這時候我們的str根本沒有一塊可以用的內(nèi)存,當(dāng)然會崩潰。
反觀數(shù)組str2,在聲明的時候就已經(jīng)在棧上分配內(nèi)存了,這時候當(dāng)然可以保存數(shù)據(jù)了。
因此,必須要為其分配內(nèi)存
char *t; t = (char*)malloc(9*sizeof(char));
接著上面的例子,我們需要理解
char *str=“hello”; char str2[]=“hello”;
這是兩種不同的操作,str是聲明一個指針指向常量區(qū)的”hello”。而str2是聲明一個str2數(shù)組用來存放一個”hello”字符串的拷貝??傊?,如果str動態(tài)申請內(nèi)存的話,那么在堆里str指向的位置就會有一個”hello”,棧里面有str2指向的”hello”,常量區(qū)還有一個”hello”。雖然都是賦值,差距卻非常大。
下面一個例子進一步證明了這一點(vs2012):
const char*s1= "sa1"; const char*s2= "sa1"; if(s1== "sa1") { cout<<"ok";//打印ok } char *oname= (char*)malloc(6*sizeof(char)); strcpy_s(oname,6,"hello"); cout<<oname;//打印hello if(oname== "hello") { cout<<oname;//不執(zhí)行 } char str2[]= "hello"; cout<<str2;//打印hello if(str2== "hello") { cout<<str2;//不執(zhí)行 }
我們知道字符串比較不能用 == 直接比較,需要用strcmp,因為上面的s1,s2,oname,str2都是指針,比較其實只是比較指針的大小。我們看到,只有上面的 s1 == "sa1"結(jié)果是true。因為s1,s2指向的都是常量區(qū)的“sa1”字符串。oname,str2分別指向堆和棧區(qū)。
那么我們看到他不僅避免了①中的實例化地址不同的問題(因為是全局唯一的),而且還避免了const帶來的內(nèi)部鏈接問題,所以這一項可能是經(jīng)過編譯器優(yōu)化過的結(jié)果。
總結(jié)
到此這篇關(guān)于C++模板非類型形參的文章就介紹到這了,更多相關(guān)C++模板非類型形參內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言左旋轉(zhuǎn)字符串與翻轉(zhuǎn)字符串中單詞順序的方法
這篇文章主要介紹了C語言左旋轉(zhuǎn)字符串與翻轉(zhuǎn)字符串中單詞順序的方法,給出了相關(guān)的兩道算法題目作為例子,需要的朋友可以參考下2016-02-02c++創(chuàng)建二維動態(tài)數(shù)組與內(nèi)存釋放問題
這篇文章主要介紹了c++創(chuàng)建二維動態(tài)數(shù)組與內(nèi)存釋放問題,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06C++深入分析內(nèi)聯(lián)函數(shù)的使用
為了消除函數(shù)調(diào)用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數(shù)調(diào)用處用函數(shù)體替換,類似于C語言中的宏展開。這種在函數(shù)調(diào)用處直接嵌入函數(shù)體的函數(shù)稱為內(nèi)聯(lián)函數(shù)(Inline Function),又稱內(nèi)嵌函數(shù)或者內(nèi)置函數(shù)2022-04-04