C++模板總結
C++模板
模板定義:模板是實現程式碼重用機制的一種工具,它可以實現型別引數化,即把型別定義為引數,從而實現了真正的程式碼可重用性。
模板分類:函式模板和類模板。函式模板針對引數型別不同的函式;類模板僅針對資料成員和成員函式型別不同的類。
使用模板目的:讓程式設計師編寫與型別無關的程式碼。
注意:模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。即不能在區域性範圍,函式內進行,如不能在main函式中宣告或定義一個模板。
函式模板
函式模板形式:
template< class 形參名, class 形參名, ……> 返回型別 函式名(引數列表){ 函式體 }
其中template和class是關鍵字,class可用typename關鍵字替代,此處typename和class沒區別,<>括號中的引數叫模板形參,模板形參和函式形參很像,模板形參不能為空
舉例:template < class T> void swap(T &a, T &b) {};,當呼叫此模板函式時,型別T會被呼叫時的型別所替代,如swap(a, b),a和b為int時,模板函式swap中的形參T被int替換,模板函式為swap(int &a, int &b);而a和b為double時,模板函式swap中的形參T被double替換,模板函式為swap(double &a, double &b),這樣便實現函式與型別無關的程式碼。
注意:對函式模板而言,不存在h(int, int)這樣的呼叫,不能在函式呼叫的引數中指定模板形參的型別,對函式模板的呼叫應使用實參推演來進行,即h(2, 3)或int a, b;h(a, b)可以。
類模板
類模板格式如下:
template< class 形參名, class 形參名, ……> class 類名 {…};
類模板也是以template開始,後接模板引數列表,模板引數不能為空,一旦聲明瞭類模板,就可以使用類模板的形參名宣告類中的成員變數和成員函式,即可以在類中使用內建型別的地方都可以使用模板形參名來宣告。
舉例:template< class T> class A { public: T a; T b; T add(T c, T &d);};。
類模板物件的建立:如模板類A,使用模板類建立物件的方法:A< int> m;。在類A後面跟上<>尖括號並在裡面填上相應型別,這樣類A中凡是用到模板形參的地方,都會被int所代替。當類模板有兩個模板形參時,建立物件方法為A< int, double> m,型別中間用逗號隔開。
對於類模板,模板形參型別必須在型別後的尖括號中明確指定。使用A<2>錯誤,原因是類模板形參不存在實參推演的問題。
在類模板外部定義成員函式方法:template < 模板形參引數列表> 函式返回型別 類名<模板形參名>::函式名(引數列表){函式體}。
舉例:假如有兩個模板形參T1,T2的類A,含有void add()函式,則定義該函式方法:template< class T1, class T2> void A< T1, T2>::add(){}
注意:在類外定義類的成員時,template後面的模板形參應與要定義的類的模板形參一致。模板的宣告或定義只能在全域性,名稱空間或類範圍內進行。不能在區域性範圍,函式內進行。
模板形參
型別:型別形參,非型別形參,模板形參。
型別形參
1)型別形參由關鍵字class或typename後接說明符構成,如template void h(T a){};,其中T就是型別形參,型別形參名可有使用者自己確定。不能為同一個模板型別形參指定兩種不同的型別。對函式,直接報錯,對類中函式,則可能計算出錯。
非型別形參
1)非型別模板形參:模板的非型別形參也就是內建型別形參,如template< class T, int a> class B{};,其中int a就是非型別的模板形參。
2)非型別形參在模板定義的內部是常量值,即說非型別形參在模板的內部是常量。
3)非型別模板的形參只能是整型,指標和引用,如double,String,String *等都不允許。但double ,double &,物件引用或指標可以。
4)呼叫非型別模板形參的實參必須是一個常量表達式,即必須在編譯時計算出結果。
5)注意:任何區域性物件,區域性變數,區域性物件地址,區域性變數的地址都不是一個常量表達式,都不能用作非型別模板形參的實參。全域性指標,全域性變數,全域性物件也不是常量表達式,不能做非型別模板形參的實參。
6)全域性變數的地址或引用,全域性物件的地址或引用const型別變數是常量表達式,可以用作非型別模板形參的實參。
7)sizeof表示式結果為常量表達式,可做非型別模板形參的實參。
8)當模板形參時整型時,呼叫該模板時的實參必須為整型,且編譯期間是常量,如對template< class T, int a>class A{},若有int b,A< int, b> m出錯,因為b不是常量,當為const int b,則正確。
9)非型別形參一般不應用與函式模板中,如template< class T, int a> void h(T b){},使用h(2)錯誤,解決方法,使用顯示模板實參,用h< int, 3>(2)能實現把非型別形參a設定為整數3。
10)非型別模板形參的形參與實參之間允許轉換情況:
1、允許從陣列到指標,從函式到指標的轉換。舉例:template< int *a > class A{}; int b[1]; A< b > m,陣列到指標轉換。
2、const修飾符的轉換。舉例:template< const int a> class A{}; int b; A<&b> m,int 到const int *轉換。
3、提升轉換。舉例:template< int a> class A{}; const short b = 2; A< b > m,從short到int提升轉換。
4、數值轉換。舉例:template< unsinged int a> class A{}; A< 2 > m,從int到unsigned int轉換。
5)常規轉換。
模板形參
模板形參表示一個未知的型別。模板型別形參可作為型別說明符用在模板中的任何地方,與內建型別說明符或類型別說明符的使用完全相同,即可以用於指定返回型別,變數宣告等。
typename
typename是一個C++程式設計語言中的關鍵字。當用於泛型程式設計時是另一術語“class”的同義詞。這個關鍵字用於指出模板宣告(或定義)中的非獨立名稱的型別,而非變數名。
C++規定:無論何時,如果使用一個依賴於模板引數的型別時,而且你想要使用這個型別的成員函式本身就是一個型別,就必須在整個名字前加上typename。舉例:template< class T > typename Vec< T >::iterator Vec< T >::erase(iterator it){}。無論什麼時候,如果你使用一個依賴於模板引數的型別時,如vector< T >,而且你想要使用這個型別的成員時,如size_type,他本身也是一個型別時,必須在整個名字之前加上typename,以便讓系統知道要把這個名字當成型別來對待。舉例:typedef typename vector< T >::size_type vec_sz;。
typedef
typedef常用來定義一個識別符號及關鍵字的別名,它是語言編譯過程的一部分,但它並不實際分配記憶體空間。
1)定義一種型別的別名,而不只是簡單的巨集替換。可以用作同時宣告指標型的多個物件。#define只是簡單的字串替換而typedef則為一個型別起新名字。
舉例:char *pa, pb; 注意不是兩個指標變數;而是用typedef char *PCHAR; PCHAR pa, pb;則為兩個指標變數。
2)在舊的C程式碼中,幫助struct。以前程式碼中struct新物件時,必須帶上struct,形式為struct 結構名 物件名。
舉例:struct tP{int x; int y}; 定義物件為struct tP tt1;而在C++中直接結構名 物件名,如tP tt1;即可。當結構體如下定義:typedef struct tP{int x, int y}P;時,則可以是用P pp1定義物件,方便。此功能在C++中無太大作用。
3)用typedef定義與平臺無關的型別。
舉例:定義一個REAL的浮點型別,使其在目標平臺上表示最高精度型別,則typedef long double REAL,在不支援long double平臺,更改為typedef double REAL即可。跨平臺時,只要改typedef本身,不用對其他原始碼做修改。因為typedef是定義一種型別的新別名,不是簡單的字串替換,故比巨集更穩健。此優點在我們寫程式碼的過程中可以減少不少程式碼量。
4)為複雜的宣告定義一個新的簡單的別名。方法:在原來的聲明裡逐步用別名替換一部分複雜宣告,如此迴圈,把帶變數的部分留到最後替換,得到的即為原宣告的最簡化版。
舉例:void (*b[10])(void(*)());,變數名為b,先替換右邊括號裡的,pFunParam為別名一:typedef void (*pFunParam)();,在替換左邊的變數b,pFunx為別名二:typedef void (*pFunx)(pFunParam);,原宣告的最簡化版為:pFun b[10];。
複雜的宣告可以使用“右左法則”:從變數名看起,先往右,再往左,碰到一個圓括號就跳轉閱讀的方向;括號內分析完就跳出括號,還是按先右後左的順序,如此迴圈,直到整個宣告分析完。
define
1)用於定義變數:如#define PI 3.1415,編譯器在處理這個程式碼之前會對PI進行處理,替換為3.1415,它與const有區別,它的實質是簡單的文字替換,並不是作為一個量來使用。
2)用於對函式進行定義:如#define MAX(a, b) ((a) > (b) ? (a) : (b)),這個定義是比較兩個數大小,此函式沒有型別檢查功能,它與模板相像,但沒函式模板安全。在使用巨集函式時,一定要養成良好的習慣和良好的程式碼編寫風格,建議所有層次都加上括號。
3)define用於單行定義:如#define A(x) ##x或#define B(x) #@x或#define C(x) #x,如果假設x = 1,則A(1)就是1,B(1)就是1,C(1)就是1。
4)define用於多行定義:如下,切記每行都要加\。
#define MACRO(arg1, arg2) do { \
test1; \
test2; \
}while (0);
5)定義巨集和取消巨集定義的方法:定義一個巨集使用#define,取消一個巨集定義使用#undef。
6)使用巨集進行條件編譯:格式:#ifdef … (#else) … #endif。如下例子:
#ifdef HEL
#define WR 1
#else
#define WR 0
#endif
7)用define來處理標頭檔案被標頭檔案或者原檔案包含的情況:因標頭檔案包含可以巢狀,那麼c檔案有可能包含多次同一個檔案,就會出現重複定義問題,可以使用條件編譯開關來避免重複包含。如下:
#ifndef _TRY_H_
#define _TRY_H_
...
// 檔案內容
#endif
8)在大規模的開發過程中,特別是跨平臺和系統的軟體裡,define重要功能是條件編譯。如
#ifdef WINDOWS
......
#endif
#ifdef LINU
......
#endif
typedef和#define區別:typedef只是為了增加可讀性而為識別符號另起的新名稱,而#define原本在C中是為了定義常量,到了C++,const,enum,inline出現使它也漸漸成為了起別名的工具。主張用typedef。遵循#define定義可讀的常量以及一些巨集語句的任務,而typedef則常用來定義關鍵字、冗長的型別的別名。巨集定義只是簡單的字串代替(原地擴充套件),而typedef則不是原地擴充套件,它的新名字具有一定的封裝性,以致於新命名的識別符號具有更易定義變數的功能。
inline
在C和C++中,inline關鍵字用來定義一個類的行內函數,引入它的主要原因是用它替代C中表達形式的巨集定義。
表示式形式的巨集定義舉例:
#define Exp(var1, var)((var1)+(var2))*((var1)-(var2))
取代上述形式巨集定義原因:
1)C中使用define這種形式巨集定義是因為C語言是一個高效的語言,這種定義在形式及使用上像一個函式,但它使用前處理器實現,沒有引數壓棧,程式碼生成等一系列的操作,效率高。
2)這種巨集定義在形式上類似於一個函式,使用它,僅僅只是做前處理器符號表中的簡單替換,故不能進行引數有效性檢測,不能享受C++編譯器嚴格檢查的好處,另外它的返回值也不能被強制轉換為可轉換的合適的型別,故使用它存在一系列的隱患和侷限性。
3)在C++中引入類及類的訪問控制,這樣如果一個操作或一個表示式涉及到類的保護成員或私有成員,就不可使用這種巨集定義實現(因為無法將this指標放在合適的位置)。
4)inline推出目的:取代巨集定義,消除巨集定義缺點,同時很好繼承巨集定義優點。
inline定義的類的行內函數,函式程式碼放入符號表中,在使用時直接進行替換,像巨集一樣展開,沒有呼叫開銷,效率高。類的內斂函式是真正的函式,編譯器在呼叫時會檢查它的引數型別,保證呼叫正確。消除巨集定義隱患和侷限性。inline可以作為某個類成員,故可以使用其類中的保護成員和私有成員。inline可以完全取代表達式形式的巨集定義;但注意,行內函數一般只會用在函式內容簡單的時候,原因是行內函數的程式碼會在任何呼叫它的地方展開,如果太複雜,程式碼膨脹帶來的惡果可能大於效率提高帶來的益處。如果函式體內出現迴圈或者其它複雜的控制結構時,處理這些複雜控制結構所花費的時間遠大於函式呼叫所花的時間,此時將函式定義為內聯,則意義不大。行內函數重要使用地方是類的存取函式。
指定行內函數方法:只需要在定義函式時,增加inline,注意是定義,不是函式宣告。函式宣告加inline,沒有任何效果。對函式inline宣告,只是程式設計師對編譯系統提出的一個建議,即它是建議性的,而不是指令性的,並非一經指定為inline,編譯系統就必須這樣做。編譯系統會根據具體情況決定是否這樣做。
enum
enum(列舉型別)是C++中的一種派生資料型別,它是由使用者定義的若干列舉常量的集合。
定義格式為:enum <型別名> {<列舉常量表>}
enum:指明其後的識別符號是一個列舉型別的名字。
列舉常量表:由列舉常量構成。列舉常量或列舉成員,是以識別符號形式表示的整型量,表示列舉型別的取值。列舉常量表列出列舉型別的所有取值,各列舉常量之間以”,”間隔,且必須各不相同。取值型別與條件表示式相同。
舉例:enum week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
1)列舉常量代表該列舉型別的變數可能取的值,編譯系統為每個列舉常量指定一個整數值,預設狀態下,這個整數就是所列舉元素的序號,序號從0開始。
2)可以在定義列舉型別時為部分或全部列舉常量指定整數值,在指定值之前的列舉常量扔按預設方式取值,而指定值之後的列舉常量按次加1的原則取值。
3)各列舉常量的值可以重複。
舉例:enum fruit {apple, orange, banana = 1, peach};,則對應常量值為apple = 0, orange = 1, banan = 1, peach = 2。如enum week {Sun = 7, Mon = 1, Tue,…};,則Sun = 7,Mon = 1, Tue = 2…
4)列舉常量只能以識別符號形式表示,而不能是整型、字元型等文字常量。
使用:定義列舉型別的主要目的是:增加程式的可讀性。列舉型別最常見也是最有意義的用處之一就是用來描述狀態量。
定義格式:定義列舉型別之後,就可以定義該列舉型別的變數,如week weekday1,weekday2;也可以型別與變數同時定義(甚至型別名可省),如下格式:enum {Sun, Mon, Tue,…}weekday1,weekday2;
相關操作:
1)列舉變數的值智慧取列舉常量表中所列的值,就是整型數的一個子集;
2)列舉變數佔用記憶體的大小與整型數相同;
3)列舉變數只能參與賦值和關係運算以及輸出操作,參與運算時用其本身的整數值;舉例:
enum color_set1{RED,BLUD,WHITE,BLACK}color1,color2;
enum color_set2{GREEN,RED,YELLOW,WHITE}color3,color4;
//則允許的賦值操作如下:
color3=RED; //將列舉常量給列舉變數
color4=color3; //相同型別的列舉變數幅值,color4的值為RED
int i=color3; // 將列舉變數賦給整型變數,i值為1
int j=GREEN; // 將列舉常量賦值給整型變數,j值為0
4)允許的關係運算符有:==,<,>,<=,>=,!=等。
5)列舉變數可以直接輸出,輸出的是變數的整數值。
cout << color3; //輸出的是color3的整數值,為RED的整數值1
6)列舉型別是預處理指令#define的替代。但enum用來定義一系列巨集定義常量區別用,相當於一系列的#define * *,當然它後面的識別符號也可當做一個型別識別符號,typedef enum則是用來定義一個數據型別,那麼該型別的變數值只能在enum定義的範圍內去。
注意:列舉變數可以直接輸出,但不能直接輸入;不能直接將常量賦給列舉變數;不同型別的列舉變數之間不能相互賦值;列舉變數的輸入輸出一般都採用switch語句將其轉換為字元或字串,列舉型別資料的其他處理也往往應用switch語句,以保證程式的合法法和可讀性。同一個程式中不能定義同名的列舉型別,不同的列舉型別中也不能存在同名的命名常量。
static
在C中,1)static修飾函式中的變數(棧變數):改變變數的生存期,作用域不變仍為所在函式。只能初始化一次。2)static修飾全域性變數:限制全域性變數智慧被模組內訪問,不可以在別的模組中用extern宣告呼叫。3)static修飾函式:作用於修飾全域性變數類似,也是限制該函式只能在模組內訪問,不能在別的模組中用extern宣告呼叫。
在C++中,1)static靜態資料成員屬於整個類所有,類的所有物件共同維護;2)static靜態函式成員也屬於整個類,一般用於呼叫靜態資料成員,不能直接訪問非static成員(要指定類才行)。
static作用:1)擴充套件生存期;2)限制作用域;3)唯一性。
全域性靜態變數:
1)不會被其它檔案所訪問,修改;
2)其他檔案中可以使用相同名字的變數,不會發生衝突。
區域性靜態變數:
1)記憶體中的位置:靜態儲存區;
2)初始化:未經初始化的全域性靜態變數會被程式自動初始化為0(自動物件的值是任意的,除非他被顯示初始化);
3)作用域:作用域仍為區域性作用域,當定義它的函式或者語句塊結束的時候,作用域隨之結束。
4)當static用來修飾區域性變數時,它就改變了區域性變數的儲存位置,從原來的佔中存放改為靜態儲存區。但是區域性靜態變數在離開作用域之後,並沒有被銷燬,而是仍駐留在記憶體當中,直到程式結束,只不過不能再對其進行訪問。
靜態函式優點:
1)其他檔案中可以定義相同名字的函式,不會發生衝突;
2)靜態函式不能被其他檔案所用。
儲存說明符auto和register對應自動儲存。具有自動儲存期的變數在進入宣告的程式塊時被建立,他在該程式塊活動時存在,退出改程式塊時撤銷。
儲存說明符extern和static用來說明具有靜態儲存器的變數和函式。用static宣告的區域性變數具有靜態儲存持續期,或靜態範圍。顯然它的值在函式呼叫之間保持有效,但其名子的可視性扔限制在其區域性域內。靜態區域性物件在程式執行到該物件的宣告處時,被首次初始化。