1. 程式人生 > >C++模板總結

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宣告的區域性變數具有靜態儲存持續期,或靜態範圍。顯然它的值在函式呼叫之間保持有效,但其名子的可視性扔限制在其區域性域內。靜態區域性物件在程式執行到該物件的宣告處時,被首次初始化。