1. 程式人生 > >C++ 模板學習筆記

C++ 模板學習筆記

1.模板的概念。

我們已經學過過載(Overloading),對過載函式而言,C++的檢查機制能通過函式引數的不同及所屬類的不同。正確的呼叫過載函式。例如,為求兩個數的最大值,我們定義MAX()函式需要對不同的資料型別分別定義不同過載(Overload)版本。

//函式1.

int max(int x,inty);
{return(x>y)?x:y ;}

//函式2.
float max(float x,floaty){
return (x>y)? x:y ;}

//函式3.
double max(double x,doubley)
{return (c>y)? x:y ;}

但如果在主函式中,我們分別定義了 char a,b; 那麼在執行max(a,b);時 程式就會出錯,因為我們沒有定義char型別的過載版本。

現在,我們再重新審視上述的max()函式,它們都具有同樣的功能,即求兩個數的最大值,能否只寫一套程式碼解決這個問題呢?這樣就會避免因過載函式定義不 全面而帶來的呼叫錯誤。為解決上述問題C++引入模板機制,模板定義:模板就是實現程式碼重用機制的一種工具,它可以實現型別引數化,即把型別定義為引數, 從而實現了真正的程式碼可重用性。模版可以分為兩類,一個是函式模版,另外一個是類模版。

2.函式模板的寫法

函式模板的一般形式如下:

Template <class或者也可以用typenameT>

返回型別 函式名(形參表)
{//函式定義體 }

說明: template是一個宣告模板的關鍵字,表示宣告一個模板關鍵字class不能省略,如果型別形參多餘一個 ,每個形參前都要加class <型別 形參表>可以包含基本資料型別可以包含類型別.

請看以下程式:

//Test.cpp

#include<iostream>

usingstd::cout;

usingstd::endl;

//宣告一個函式模版,用來比較輸入的兩個相同資料型別的引數的大小,class也可以被typename代替,

//T可以被任何字母或者數字代替。

template<classT>

T min(T x,T y)

{return(x<y)?x:y;}

voidmain( )

{

    intn1=2,n2=10;

    doubled1=1.5,d2=5.6;

     cout<<"較小整數:"<<min(n1,n2)<<endl;

     cout<<"較小實數:"<<min(d1,d2)<<endl;

     system("PAUSE");

}

程式執行結果:

 

程式分析:main()函式中定義了兩個整型變數n1 , n2 兩個雙精度型別變數d1 , d2然後呼叫min( n1, n2); 即例項化函式模板T min(T x, T y)其中T為int型,求出n1,n2中的最小值.同理呼叫min(d1,d2)時,求出d1,d2中的最小值.

3.類模板的寫法

定義一個類模板:

Template <class或者也可以用typenameT >
class類名{
//類定義......
};

說明:其中,template是宣告各模板的關鍵字,表示宣告一個模板,模板引數可以是一個,也可以是多個。

例如:定義一個類模板:

// ClassTemplate.h
#ifndefClassTemplate_HH

#defineClassTemplate_HH

template<typenameT1,typenameT2>

classmyClass{

private:

     T1 I;

     T2 J;

public:

     myClass(T1 a, T2 b);//Constructor

    voidshow();

};

//這是建構函式

//注意這些格式

template<typenameT1,typenameT2>

myClass<T1,T2>::myClass(T1 a,T2 b):I(a),J(b){}

//這是void show();

template<typenameT1,typenameT2>

voidmyClass<T1,T2>::show()

{

     cout<<"I="<<I<<", J="<<J<<endl;

}

#endif

// Test.cpp

#include<iostream>

#include"ClassTemplate.h"

usingstd::cout;

usingstd::endl;

voidmain()

{

     myClass<int,int> class1(3,5);

     class1.show();

     myClass<int,char> class2(3,'a');

     class2.show();

     myClass<double,int> class3(2.9,10);

     class3.show();

     system("PAUSE");

}

最後結果顯示:

4.非型別模版引數

一般來說,非型別模板引數可以是常整數(包括列舉)或者指向外部連結物件的指標。

那麼就是說,浮點數是不行的,指向內部連結物件的指標是不行的。


template<typenameT,intMAXSIZE>

classStack{

Private:

       T elems[MAXSIZE];

};

Int main()

{

       Stack<int, 20> int20Stack;

       Stack<int, 40> int40Stack;

};

5.使用模板型別

有時模板型別是一個容器或類,要使用該型別下的型別可以直接呼叫,以下是一個可列印STL中順序和鏈的容器的模板函式

template <typename T>
void print(T v)
{
 T::iterator itor;
 for (itor = v.begin(); itor != v.end(); ++itor)
 {
  cout << *itor << " ";
 }
 cout << endl;
}

void main(int argc, char **argv){
 list<int> l;
 l.push_back(1);
 l.push_front(2);
 if(!l.empty())
  print(l);
 vector<int> vec;
 vec.push_back(1);
 vec.push_back(6);
 if(!vec.empty())
  print(vec);
}

列印結果

型別推導的隱式型別轉換
在決定模板引數型別前,編譯器執行下列隱式型別轉換:

  左值變換
  修飾字轉換
  派生類到基類的轉換

  見《C++ Primer》([注2],P500)對此主題的完備討論。

簡而言之,編譯器削弱了某些型別屬性,例如我們例子中的引用型別的左值屬性。舉例來說,編譯器用值型別例項化函式模板,而不是用相應的引用型別。

同樣地,它用指標型別例項化函式模板,而不是相應的陣列型別。

它去除const修飾,絕不會用const型別例項化函式模板,總是用相應的非 const型別,不過對於指標來說,指標和 const 指標是不同的型別。

底線是:自動模板引數推導包含型別轉換,並且在編譯器自動決定模板引數時某些型別屬性將丟失。這些型別屬性可以在使用顯式函式模板引數申明時得以保留。

6. 模板的特化
如果我們打算給模板函式(類)的某個特定型別寫一個函式,就需要用到模板的特化,比如我們打算用 long 型別呼叫 max 的時候,返回小的值(原諒我舉了不恰當的例子):
template<> // 這代表了下面是一個模板函式
long max<long>( long a, long b ) // 對於 vc 來說,這裡的 <long> 是可以省略的
{
  return a > b ? b : a;
}
實際上,所謂特化,就是代替編譯器完成了對指定型別的特化工作,現代的模板庫中,大量的使用了這個技巧。
對於偏特化,則只針對模板型別中部分型別進行特化,如

template<T1, T2>

class MyClass;

template<T1, T2>

class MyCalss<int, T2>//偏特化
7. 仿函式
仿函式這個詞經常會出現在模板庫裡(比如 STL),那麼什麼是仿函式呢?
顧名思義:仿函式就是能像函式一樣工作的東西,請原諒我用東西這樣一個代詞,下面我會慢慢解釋。
void dosome( int i )
這個 dosome 是一個函式,我們可以這樣來使用它: dosome(5);
那麼,有什麼東西可以像這樣工作麼?
答案1:過載了 () 操作符的物件,因此,這裡需要明確兩點:
  1 仿函式不是函式,它是個類;
  2 仿函式過載了()運算子,使得它的對你可以像函式那樣子呼叫(程式碼的形式好像是在呼叫比如:
  struct DoSome
  {
  void operator()( int i );
  }
  DoSome dosome;
這裡類(對 C++ 來說,struct 和類是相同的) 過載了 () 操作符,因此它的例項 dosome 可以這樣用 dosome(5); 和上面的函式呼叫一模一樣,不是麼?所以 dosome 就是一個仿函數了。

實際上還有答案2:
  函式指標指向的物件。
  typedef void( *DoSomePtr )( int );
  typedef void( DoSome )( int );
  DoSomePtr *ptr=&func;
  DoSome& dosome=*ptr;
   
  dosome(5); // 這裡又和函式呼叫一模一樣了。
當然,答案3 成員函式指標指向的成員函式就是意料之中的答案了。

8. 仿函式的用處
不管是物件還是函式指標等等,它們都是可以被作為引數傳遞,或者被作為變數儲存的。因此我們就可以把一個仿函式傳遞給一個函式,由這個函式根據需要來呼叫這個仿函式(有點類似回撥)。
STL 模板庫中,大量使用了這種技巧,來實現庫的“靈活”。
比如:
for_each, 它的原始碼大致如下:
template< typename Iterator, typename Functor >
void for_each( Iterator begin, Iterator end, Fucntor func )
{
  for( ; begin!=end; begin++ )
  func( *begin );
}

這個 for 迴圈遍歷了容器中的每一個元素,對每個元素呼叫了仿函式 func,這樣就實現了 對“每個元素做同樣的事”這樣一種程式設計的思想。

特別的,如果仿函式是一個物件,這個物件是可以有成員變數的,這就讓 仿函式有了“狀態”,從而實現了更高的靈活性。

相關推薦

C++ 模板學習筆記

1.模板的概念。 我們已經學過過載(Overloading),對過載函式而言,C++的檢查機制能通過函式引數的不同及所屬類的不同。正確的呼叫過載函式。例如,為求兩個數的最大值,我們定義MAX()函式需要對不同的資料型別分別定義不同過載(Overload)版本。 //函式1.

C++基礎學習筆記:自定義陣列模板

//!時間:2017年9月12日(週二)下午 //!內容:陣列模板類 /* 修改:2017年9月13上午 成員方法中delete未正確匹配 改進:2017年9月13晚上 陣列總量改為固定 */ #define _CRTDBG_MAP_ALLOC #include <iostream>

C++ set學習筆記

all pri cto 等於 中序 center type 節點 begin Stl~(multi)set set集合容器:實現了紅黑樹的平衡二叉檢索樹的數據結構,插入元素時,它會自動調整二叉樹的排列,把元素放到適當的位置,以保證每個子樹根節點鍵值大於左子樹所有節點的鍵

[C/C++] C++ Primer學習筆記

轉義 寫到 十六進制 程序 結果 否則 筆記 end 情況 下面記錄我每天看書學到的以前不太清楚的概念和用法: Day 1 endl:具有輸出換行的效果,並刷新與設備相關聯的緩沖區。 註:在調試程序過程中插入的輸出語句都應刷新輸出流,否則可能會造成程序崩潰,將會導致程序出錯

C++ Primer 學習筆記_5_變量和基本類型(續2)

key 情況 boa 類和對象 類定義 優點 splay 查看 變量定義  變量和基本類型 七、枚舉 枚舉不但定義了整數常量集,並且還把它們聚集成組。 枚舉與簡單的const常量相比孰優孰劣, 通過以下一段代

c 語言學習筆記(一)基礎

lac alert https 內容 con 標記 prime c++ prime c基礎 近期工作上有對C語言算法上的需求,所以要學C,上學的時候沒學過,只學過半年的Java(我是專升本,本科學的材料),2015年工作後也學過C,那時候的需求是能看懂就可以,也就沒深入研究

C++ Primer 學習筆記與思考_7 void和void*指針的使用方法

能夠 amp space turn begin member use mem urn (一)void的含義 void的字面意思是“無類型”,void差點兒僅僅有“凝視”和限制程序的作用,由於從來沒有人會定義一個void變量,讓我們試著來定義: void a;

C語言學習筆記 (003) - C/C++中的實參和形參(轉)

變化 避免 影響 學習筆記 ++ nbsp 過去 情況 真的 今天突然看到一道關於形參和實參的題,我居然不求甚解。藐視過去在我的腦海裏只有一個參數的概念,對於形參和實參的區別還真的不知道,作為學習了幾年C++的人來說,真的深深感覺對不起自己對不起C++老師 T。T 我

C語言學習筆記

變量 col 語言學 指針變量 筆記 數組名 nbsp span 就是 一、指針 指針就是地址,指針變量是用來存放地址的變量,把誰的地址存放在指針變量中,就說此指針變量指向誰。 1.一維數組 一維數組名代表數組首元素的地址 &:取地址運算符。&a 是變量 a

梓益C語言學習筆記之常用鏈表操作函數

C語言 鏈表操作 梓益C語言學習筆記之常用鏈表操作函數一、創建鏈表void link_creat_head(STU **p_head,STU *p_new){ STU *p_mov=*p_head; if(*p_head==NULL) //當第一次加入鏈表為空時,head執行p_new { *

梓益C語言學習筆記之鏈表&動態內存&文件

C語言 鏈表 梓益C語言學習筆記之鏈表&動態內存&文件一、定義: 鏈表是一種物理存儲上非連續,通過指針鏈接次序,實現的一種線性存儲結構。二、特點: 鏈表由一系列節點(鏈表中每一個元素稱為節點)組成,節點在運行時動態生成(malloc),每個節點包括兩個部分: 存儲數據元素的數據域 存儲下一個節點地址的

梓益C語言學習筆記之常用字符串操作(sscanf & strtok)

C語言 字符串操作 梓益C語言學習筆記之常用字符串操作(sscanf & strtok)一、sscanf int sscanf(const char *buf,const char *format, …); \\從buf指定的內存區域中讀入信息 例: int a, b, c; ssc

c語言學習筆記.結構體.

成員訪問運算符 div 成員 bsp tro truct 年齡 可用 語言學 結構體:   一種用戶自定義的可用的數據類型,它允許您存儲不同類型的數據項。 定義/聲明: struct 類型名{ 成員1; 成員2; ... 成員n; } 變量

C# Linq 學習筆記

list size color mar div key code any esc 剛剛學習了 Siki老師 的C#教程Linq部分,以下是筆記 需要引用命名空間 1 using System.Linq; 然後我們需要準備數據 武林高手類 /// <summary

C語言學習筆記之字符串拼接的2種方法——strcat、sprintf

fir str return print 文章 %s rst stdlib.h 字符串拼接 本文為原創文章,轉載請標明出處 1. 使用strcat進行字符串拼接 #include <stdio.h> #include <stdlib.h> #incl

c語言學習筆記 if語句的條件判斷

圖片 分享 筆記 賦值 if條件 語句 int image 運算符 可能經常會看到錯誤的if語句示範,比如這樣的: if(a=6) {   printf("hello"); } if語句塊執行的條件是if條件的運算結果不是0則執行if語句塊。 a=6這是個賦值運算符

c語言學習筆記-if語句塊一定要加分號

學習 語言 括號 語句 執行 -i c語言學習 c語言 分號 if(a>6) printf("hello");//語句1 printf("world");//語句2 當a>6的時候,執行的分支語句是語句1,而不是語句1和語句2,雖然結果是語句1和語句2都被執

C語言學習筆記 〗(一) HelloWorld

文件頭部 標準 變量 標準輸出 class 語言學 你好 a.out 格式 前言 本文為c基礎入門學習筆記 正文 HelloWorld #include <stdio.h> //標準輸出流 int main() //每種語言都有一個執行入口,main方法就是其

C語言學習筆記之位運算求余

nbsp 位運算 sdn 縮小 .net 一次 得出 ngs 有效 我們都知道,求一個數被另一個數整除的余數,可以用求余運算符”%“,但是,如果不允許使用求余運算符,又該怎麽辦呢?下面介紹一種方法,是通過位運算來求余,但是註意:該方法只對除數是2的N次方冪

C語言學習筆記(4)—— 數據類型的使用

循環小數 強制類型轉換 進行 代碼 size 圖片 unsigned 問題 d+  在程序設計語言裏,我們會把數據分為各種各樣的類型,為什麽會有數據類型之分呢?計算機中,所有的數據都會表示成二進制數的形式,對於同樣的一個二進制數,數據類型不同,它表示的數據就是不同的。也就是