一,模板的概念。
引入模板的原因:
我們已經學過重載,對重載函數而言,C++的檢查機制能通過函數參數的不同及所屬類的不同。正確的調用重載函數。例如,為求兩個數的最大值,我們定義MAX()函數需要對不同的資料類型分別定義不同重載版本。
Int max(int x,int y);
{return(x>y)?x:y ;
}
float max( float x,float y){
return (x>y)? x:y ;}
double max(double x,double y)
{return (c>y)? x:y ;}
但如果在主函數中,我們分別定義了 char a,b;
在執行max(a,b);時 程式就會出錯,因為我們沒有定義char類型的重載版本。
現在,我們再重新審視上述的max()函數,它們都具有同樣的功能,即求兩個數的最大值,能否唯寫一套代碼解決這個問題呢?這樣就會避免因重載函數定義不全面而帶來的調用錯誤。為解決上述問題C++引入模板機制,模板定義:模板就是實現代碼重用機制的一種工具,它可以實作類別型參數化,即把類型定義為參數,從而實現了真正的代碼可重用性。模板分類:磨板分為函數模板後,當編譯系統發現了一個對應的函數調用事,將根據實參的類型來確認是否匹配函數模板中對應的形參然後產生一個重載函數,稱該重載函數為模板函數。函數模板與模板函數的區別: 二者區別可以類比 類與對象的區別。函數 模板與類相似是模板的定義,而模板函數與對象相似。是函數模板的執行個體,具有程式碼。佔用記憶體空間。同樣,在說明了一個類模板後,也可以建立類模板的執行個體即產生模板類。類模板與模板類的區別是:類模板是模板的定義,不是一個實在的類,模板類才是實實在在的類。
二、函數模板與模板憾事
函數模板的一般生命形式如下:
template<class類型形參表>
傳回型別 函數名(形參表)
{//函數定義體 }
說明: templarte是一個聲明模板的關鍵字,表示聲明一個模板關鍵字class不能省略,如果類型形參多餘一個 ,每個形參前都要加class <類型 形參表>可以包含基礎資料型別 (Elementary Data Type)可以包含類類型.
請看以下程式:
#include<iostream.h>
template<class T> /*函數模板聲明*/
T min(T x , T y)
{if(x<y ) return x;
else return y;}
void main( )
{ int n1=2,n2=10;
double d1=1.5,d2=5.6;
cout<<”較小整數:”<<min(n1,n2)<<endl;//執行個體化 min模板函數比較兩整數
cout<<”較小實數:”<<min(d1,d2)<<endl;// 執行個體化min模板函數比較兩雙精確度數
}
程式運行結果: 較小整數:2
較小實數:1.2
程式分析:main()函數中定義了兩個整型變數n1 , n2 兩個雙精確度類型變數d1 , d2然後調用min( n1, n2); 即執行個體化函數模板T min(T x, T y)其中T為int型,求出n1,n2中的最小值.同理調用min(d1,d2)時,求出d1,d2中的最小值.
可用表示函數模板執行個體化過程
函數模板min(x,y)
模板函數min(n1,n2)int型
模板函數min(d1,d2) double型
若main()函數中加一條cout<<min(n1,d1)<<endl;
程式將會出錯,原因是模板函數T的各參數之間必須保持完全一致的類型,並不具有隱式類型轉換功能.
三,類模板與模板類
1.定義一個類模板:
template<class類型形參表>
class類名{
//類定義......
};
其中,template是聲明各模板的關鍵字,表示聲明一個模板,模板參數可以是一個,也可以是多個,但應是抽象化的結果,不應是具體的(例int,float等)類型,成員函數的參數或傳回型別,前面要加上形參類型.
例如:定義一個類模板:
template<classT1,classT2>
class myclass{T1 I;//
T2 j;//
Public:
Myclass(T1 a,T2 b)//
{I=a; j=b;}
void show( )
{cout<<”I=”<<”j=”<<j<<endl;}};
在主函數中若定義了一模板類 myclass<int,double>並且聲明一個類對象ob1(1 引用語句:myclass<int,double >ob(2,0.1);注意:myclass<int,double>執行個體化了類模板,即將T1執行個體為int 類型,T2為double 類型,這樣我們就得到了一個模板類.然後就可以定義類對象ob1並初始化.
還可以定義另一個模板類如: myclass<double ,char>
可通過表示類模板與模板類的關係
類模板myclass<T!,T2>
模板類myclass(int,double)
模板類nyclass<double,char>
總結:函數模板是一類函數的抽象,代表了一類函數,這一類函數具有相同的功能,代表一 具體的函數,能被類對象調用,而函數模板絕不能被類對象調用.
類模板是對類的抽象,代表一類類,這些類具有相同的功能,但資料成員類型及成員函數傳回型別和形參類型不同.模板類是類模板類的執行個體.代表一具體的類,可以定義類對象 ,而不能給類模板定義對象.
-------------------------------
名正言順
中國有句古話:名不正則言不順。一樣東西,名字如果用的不好,不但聽起來不舒服,而且真實的本質也容易被掩蓋。如果一樣東西有一個好名字,我們就能更容易的記住它和理解它。
在現代漢語中,許多詞的重點都在後面,比如下面我們經常看到的兩個詞語。
冰雪聰明。
聰明冰雪。
冰雪聰明強調的是聰明,她像冰雪一樣的聰明。
聰明冰雪強調的是冰雪,她很聰明,看上去更是冰雪般的玲瓏剔透純潔。
在C++中有好幾個這樣的術語,但是我們很多時候用的並不正確,幾乎是互相替換混淆使用。下面我想徹底辨清幾個術語,這樣就可以避免很多概念上的混淆和使用上的錯誤。
這幾個詞是:
函數指標——指標函數
數組指標——指標數組
類模板——模板類
函數模板——模板函數
最終在使用中,我們就可以讓它們實至名歸,名正言順。
1.函數指標——指標函數
函數指標的重點是指標。表示的是一個指標,它指向的是一個函數,例子:
int (*pf)();
指標函數的重點是函數。表示的是一個函數,它的傳回值是指標。例子:
int* fun();
2.數組指標——指標數組
數組指標的重點是指標。表示的是一個指標,它指向的是一個數組,例子:
int (*pa)[8];
指標數組的重點是數組。表示的是一個數組,它包含的元素是指標。例子;
int* ap[8];
3.類模板——模板類(class template——template class)
類模板的重點是模板。表示的是一個模板,專門用於產生類的模子。例子:
template <typename T>
class Vector
{
…
};
使用這個Vector模板就可以產生很多的class(類),Vector<int>、Vector<char>、Vector< Vector<int> >、Vector<Shape*>……。
模板類的重點是類。表示的是由一個模板產生而來的類。例子:
上面的Vector<int>、Vector<char>、……全是模板類。
這兩個詞很容易混淆,我看到很多文章都將其用錯,甚至一些英文文章也是這樣。將他們區分開是很重要的,你也就可以理解為什麼在定義模板的標頭檔.h時,模板的成員函數實現也必須寫在標頭檔.h中,而不能像普通的類(class)那樣,class的聲明(declaration)寫在.h檔案中,class的定義(definition)寫在.cpp檔案中。請參照Marshall Cline的《C++ FAQ Lite》中的[34] Container classes and templates中的[34.12] Why can't I separate the definition of my templates class from it's declaration and put it inside a .cpp file? URL地址是http://www.parashift.com/c++-faq-lite/containers-and-templates.html#faq-34.12
我將幾句關鍵的段落摘錄如下,英文很好理解:
In order for the compiler to generate the code, it must see both the template definition (not just declaration) and the specific types/whatever used to "fill in" the template. For example, if you're trying to use a Foo<int>, the compiler must see both the Foo template and the fact that you're trying to make a specific Foo<int>.
Suppose you have a template Foo defined like this:
template<class T>
class Foo {
public:
Foo();
void someMethod(T x);
private:
T x;
};
Along with similar definitions for the member functions:
template<class T>
Foo<T>::Foo()
{
...
}
template<class T>
void Foo<T>::someMethod(T x)
{
...
}
Now suppose you have some code in file Bar.cpp that uses Foo<int>:
// Bar.cpp
void blah_blah_blah()
{
...
Foo<int> f;
f.someMethod(5);
...
}
Clearly somebody somewhere is going to have to use the "pattern" for the constructor definition and for the someMethod() definition and instantiate those when T is actually int. But if you had put the definition of the constructor and someMethod() into file Foo.cpp, the compiler would see the template code when it compiled Foo.cpp and it would see Foo<int> when it compiled Bar.cpp, but there would never be a time when it saw both the template code and Foo<int>. So by rule above, it could never generate the code for Foo<int>::someMethod().
關於一個預設模板參數的例子:
template <typename T = int>
class Array
{
…
};
第一次我定義這個模板並使用它的時候,是這樣用的:
Array books;//我認為有預設模板參數,這就相當於Array<int> books
上面的用法是錯誤的,編譯不會通過,原因是Array不是一個類。正確的用法是Array<> books;
這裡Array<>就是一個用於預設模板參數的類模板所產生的一個具體類。
4.函數模板——模板函數(function template——template function)
函數模板的重點是模板。表示的是一個模板,專門用來生產函數。例子:
template <typename T>
void fun(T a)
{
…
}
在運用的時候,可以顯式(explicitly)生產模板函數,fun<int>、fun<double>、fun<Shape*>……。
也可以在使用的過程中由編譯器進行模板參數推導,幫你隱式(implicitly)產生。
fun(6);//隱式產生fun<int>
fun(8.9);//隱式產生fun<double>
fun(‘a’);// 隱式產生fun<char>
Shape* ps = new Cirlcle;
fun(ps);//隱式產生fun<Shape*>
模板函數的重點是函數。表示的是由一個模板產生而來的函數。例子:
上面顯式(explicitly)或者隱式(implicitly)產生的fun<int>、fun<Shape*>……都是模板函數。
關於模板本身,是一個非常龐大的主題,要把它講清楚,需要的不是一篇文章,而是一本書,幸運的是,這本書已經有了:David Vandevoorde, Nicolai M. Josuttis寫的《C++ Templates: The Complete Guide》。可惜在大陸買不到紙版,不過有一個電子版在網上流傳。
模板本身的使用是很受限制的,一般來說,它們就只是一個產生類和函數的模子。除此之外,運用的領域非常少了,所以不可能有什麼模板指標存在的,即指向模板的指標,這是因為在C++中,模板就是一個代碼的代碼生產工具,在最終的代碼中,根本就沒有模板本身存在,只有模板具現出來的具體類和具體函數的代碼存在。
但是類模板(class template)還可以作為模板的模板參數(template template parameter)使用,在Andrei Alexandrescu的《Modern C++ Design》中的基於策略的設計(Policy based Design)中大量的用到。
template< typename T, template<typename U> class Y>
class Foo
{
…
};
從文章的討論中,可以看到,名字是非常重要的,如果對名字的使用不恰當的話,會引起很多的麻煩和誤解。我們在實際的程式中各種標識符的命名也是一門學問,為了清晰易懂,有時候還是需要付出一定的代價。
最後提醒:在本文的幾個術語中,語言的重心在後面,前面的詞是作為形容詞使用的。
吳桐寫於2003.6.1