類模版 具體化 特化 部分特化 友元
提前說明一下:
寫類模版時把 <型別引數 >與 類名 看成一個整體 , 比較好理解;
比如 stack<int> 是一個類, stack<double> 是另一個類.
那麼stack 呢?? 是一個用於生成類的模版,
即類模版是一個類的藍圖, 涉及到具體的類(stack<int>)則由編譯器給我們生成;
如果看這些東西比較吃力, 建議先使用一下標準庫的:vector, queue,stack 再學習模版
<這句話可以在看完模版後再回過頭來看看>:
提前先說一下,全特化本質上就是一個例項,而部分特化還是一個模版;
每個類模版例項化的類都是一個獨立的類:意思是可以隨意新增任意成員變數與函式
首先1個常規的通用模版類:(具體化或特化 在下面介紹)
Stack.h
/* Stack.h 類模版基本概念: 如果把類看成物件的藍圖 , 則類模版可看作類的藍圖; template <typename T> 告訴編譯器,你就按這個方式來生成這個類; 到時如果有Stack<int> si; 編譯器就會給生成 Stack<int> 這個類 (類模版在使用時才會生成程式碼,所以常常把模版的宣告與定義放在一起) *把Stack<int> 看成一個整體,即Stack<int>是1個類, Stack是Stack<int>的模版; 如有Stack<double> 又會生成1個類,這些程式碼生成工作是編譯器給咱作的. 下面的例子中,把成員函式都放到類外定義了, 如在類內定義則不需要加Stack::<T>, 直接使用Stack,就像常規類定義; 編寫下面程式碼時,新手需時時注意一點,模版不是類,模版的目的是讓編譯器生成類. 所以 下面程式碼把 Stack<T> 看成是一個類,而不是Stack . 如果看T比較麻煩,可以想像Stack<int> ; */ /* 這是一個正常的類: class Stack{}; 模版類需要加一個模版頭去宣告一下: template <typename T> (typename 表示型別名稱是 T,T 表示一個型別,你就當成一個佔位符就行了,那麼 T 到底是什麼型別呢? 由外部傳入) */ //模版頭,意思是下面這個類是一個模版.到時編譯器將生成一個 Stack<XX> 類,例如Stack<int>; template <typename T> class Stack { private: enum {MAX=10}; T * items; int stackSize; int top; public: //先介紹所有的成員函式在類外部定義的方式,類內定義與正常的類相同 explicit Stack(int ss = MAX); Stack(const Stack &s); ~Stack(); Stack& operator= (const Stack &s); bool isEmpty(); bool isFull(); bool push(const T& t); bool pop(T& t); void show() const; }; template <typename T> //這裡也必須寫,暗示這是一個模版,不是一個類.除非定義在類內部. Stack<T>::Stack(int ss) : top(0),stackSize(ss),items(0) //注意:Stack<T>:: 而不是Stack { items = new T[stackSize]; } template <typename T> //注意所有的成員函式在類外部定義的時候都要額外新增模版頭 Stack<T>::Stack(const Stack &s) { stackSize = s.stackSize; top = s.top; items = new T[stackSize]; for(int i = 0; i < top ; ++i) items[i] = s.items[i]; } template <typename T> Stack<T>::~Stack() { if(items) delete[] items; } template <typename T> Stack<T>& Stack<T>::operator=(const Stack<T> & s) //函式引數Stack<T>& ,而不是Stack&.因為Stack是一個模版 { if(this == &s) return *this; if(items) delete[] items; stackSize = s.stackSize; top = s.top; items = new T[stackSize]; for(int i =0 ; i < top ; ++i) items[i] = s.items[i]; return *this; } template <typename T> bool Stack<T>::isEmpty() { return top == 0; } template <typename T> bool Stack<T>::isFull() { return top == MAX; } template <typename T> bool Stack<T>::push(const T &t) { if(top < MAX) { items[top++] = t; return true; } return false; } template <typename T> bool Stack<T>::pop(T &t) { if(top > 0){ t = items[--top]; return true; } return false; } template <typename T> void Stack<T>::show() const { for(int i = 0; i < top ; ++i) std::cout << items[i] << std::endl; }
以上需要注意的是: 每個成員函式的定義前都是 Stack<T>:: , 而不是Stack ; 同時每個成員函式上方都加了模版頭 template<typename T>; (把template <typename T> 當成是1個引數列表) 原因是當加上了模版頭template<typename T> 後, Stack已經是1個模版而不是類. 真正的類是編譯給我們生成的, 何時生成呢? 比如 當使用了 Stack<string> 時 , 即生成了1個 Stack<string> 類; 如果函式在類內定義就簡單很多了, 不需要再加Stack<T>:: 字首, 也不需要在每個函式上放新增template <typename T> 此時: Stack<int> si; 將生成一個類 class Stack<int>; Stack<double> sd; 又生成一個類 class Stack<double>; 總之把<>內的型別引數列表與模版名Stack 看成一個整體會比較好理解模版生成的類; 再次強調 Stack 不是類,而是模版; //main.cpp //其他的方法就不測了.主要說明類模版 /* 編譯器將例項化一個類; 注意Stack<int> 這種方式.這是一個類. 比一般的類多了<int>. 當編譯器看到這條語句後,將生成一個class Stack<int> 類; 此時成員變數 T *items 將被替換成 int *items; 如果不是特別理解類模版的使用方式,建議先去使用一下類似 : vector , array , queue 這些 標準庫的類. */ Stack<int> si(100); //例項化了一個class Stack<int> 類 for(int i= 0 ; i < 10; ++i) si.push(i); Stack<double> sd(10); //例項化一個class Stack<double> 類 /* 請注意請注意請注意: 類模版例項化的類都是一個獨立的類. 比如Stack<double> 與 Stack<int> 沒任何關係; 你可以這麼理解 class Stack_double; class Stack_int; 這2個類是沒有任何關係的; 模版僅僅是讓編譯器來幫我們生成類 */
類模版可以新增非型別形參: 非型別引數 只能是常量表達式;
//非型別形參的模版
/*
說明:
具體函式定義就不寫了;
* 注意模版頭: template <typename T, int n>
後面多加了1個int n; 這種非型別引數可以是常量表達式:
比如可以這樣: Array<double, 10> arr_10;
此時類 :Array<double , 10> 的內部成員變數ar的型別是: double ar[10];
* 需要注意的是如有 :
Array<int,10> int_10;
Array<int,11> int_11;
兩個物件時, 編譯器將生成class Array<int,10> , class Array<int,11> 兩個類;
而不是像上面的:Stack<int> s1(10); Stack<int> s2(11); 將只生成1個類;
請記住,把Array<int,10> 看成一個整體 .
例如像這麼一個類:class Array_int_10 與 class Array_int_11 是一個類嗎? 不是!!!
*/
template <typename T,int n >
class Array
{
private:
T ar[n];
public:
Array(){};
explicit Array(const T& a);
virtual T& operator[](int );
virtual T operator [](int i)const;
};
一句話說明特化:特化本質上就是一個自己寫了一個例項;
下面說明一下特化(具體化) : 有些囉嗦,將就著看吧 , 至少能把問題說明白
特化是對某一個模版進行的特殊處理(也稱為例項化);
首先假設有2個類模版在使用時 如:
Stack<double> sd; //例項化了一個 class Stack<double>
Array<int,10> arr_10; //例項化了一個 class Array<int,10>
這樣稱為例項化.這樣的例項化的是編譯器生成的.
編譯器將生成 class Stack<double> 與 class Array<int,10> 這2個類;
特化的產生是因為有些時候,當我們使用一些型別比如const char* 時,沒有辦法能很好的處理譬如比大小(如果在之前的模版類中使用了 > 或 < 來比大小);
那麼如果還是使用編譯器產生的程式碼時,會不合適.const char* 將比較的是地址,這是沒有意義的;
特化能幫我們解決這個問題.
特化是這麼一種情況, 由我們自己提供程式碼(自己寫一個類),讓編譯器別瞎折騰了(不再生成類程式碼). 此時將使用我們自己寫的類,來處理const char*這類問題;
即當編譯器發現有模版類物件使用時, 將分別生成Stack<double>類, Array<int,10> 類.這種生成稱為例項化;
特別說明下,類模版不使用時將不生成程式碼,可以檢視相關的 .o 檔案 , 這也是為什麼通常模版類宣告與定義都放在標頭檔案中;
不要把 具體化(特化)的類 與 通用模版類 混淆. 他們之間不是過載的關係, 可以看成完全不同的類來對待,唯一的聯絡只在於
具體化的類 把 通用模版類的 引數確定了 ,以及編譯器將不再生成具體化的程式碼了.
再次強調一下, 特化的類是對某一個模版類進行的引數確定;
比如:
通用模版類 :
template <typename T>
Stack{};
(特化)具體化的類 :
template <> //不需要型別引數了 , 特化的類也要加上這個模版頭, 說明這是一個特化的類,是對某個模版的特化(特殊處理)
Stack<int> //注意, 後面加了一個<int> ,這個int意思是把通用模版的 T 替換了;
{
};
這裡, class Stack<int> 中的 int 把通用模版類的 T 確定了, 即Stack<int> 已經定義了;
此時編譯器在看到Stack<int> 時將不會用通用模版類來生成Stack<int> 這個類的程式碼了(例項化);
這樣以後當使用 Stack<int> si ; 時 ,將使用我們自己定義的特化的類了;
強調一下特化的類比如Stack<int> 在本質上就是我們接管了編譯器的工作,意思是我自己的例項化了一個類,編譯器你就別管了,直接用就得了;
下面具體說明:
/*
有如下類 , 需要傳入2個型別引數的模版類
*/
template <typename T, typename U>
class Pair
{
private:
T a;
U b;
public:
T& first();
U& second();
T first() const {return a;}
U second() const {return b;}
Pair(const T& t, const U& u) : a(t),b(u){ std::cout << "Pair(const T& t, const U& u) " << endl;}
Pair() { cout << "Pair()" << endl;}
//類內定義成員函式
void compare() const
{
if( a >= b)
std::cout <<"a :" <<a << endl;
else
std::cout << "b :" <<b << endl;
}
};
//類外定義成員函式
template <typename T, typename U>
T& Pair<T,U>::first(){
return a;
}
template<typename T, typename U>
U& Pair<T,U>::second()
{
return b;
}
//main.cpp
/*
如果是基本型別,可以使用>=時或者當物件支援operator>=() 時,這個模版類沒有問題;
但是當T和U 都是char *或const char* 需要字串比較時,這個模版類就失效了
*/
Pair<int,double> p(11,5.5); //生成了class Pair<int,double> 類;
p.compare(); //ok
//下面compare的失效;
Pair<const char*, const char*> p2("bbb","aaa"); //生成class Pair<const char*,const char*>;
p2.compare(); //比較的是地址
/*
再次說明一下,模版只有在使用的時候才會例項化:
例如上面的: Pair<int,double> p(11,5.5) 此時將例項化class Pair<int,double>;
下面的例子是特化:
*/
/*
Pair.h
由於上面由編譯器生成的Pair 不好用,此時我們可以自己做特殊處理,也叫特化;
此時需要為一個特殊的型別const char* 做一些修改;
這個版本叫做類模版的(完全)具體化. (附: 完全的意思是所有的引數已經確定了是const char*)
下面是一個專門用於const char* 型別的Pair模版;
注意模版頭 template<> 裡面是空的. 原因是我們能夠確定2個型別引數都是const char*;
上面的類模版(這個模版類也稱通用模版類):
template<typename T, typename U>
class Pair {};
特化的模版類請特別注意類的宣告:
template<> //<> 內部為空的,是因為我們知道引數已經全部確定是const char*了;部分特化後面說
class Pair<const char*, const char*> {}; //在類名後面加<> 內部填寫型別引數
首先:
template <> : 我們能夠確定T,U都是const char* , 所以為空;
class Pair<const char*, const char*> : 為什麼要這麼寫???
當使用上面的通用模版時的寫法,建立了一個物件: Pair<int,double> pid;
發現了嗎這個類Pair<int,double> 的寫法, 這個類由編譯器生成class Pair<int,double>;
現在我們告訴編譯器你別生成了(或者稱為例項化類),我已經寫好了這個類:
class Pair<const char*, const char*> ;
但是由於這個類是一個特化的類,所以必須加上template<>;
請牢記,特化的本質就是產生一個例項;
*/
template <> //這裡不一樣 , <> 空代表所有的引數全部已經確定
class Pair<const char*, const char*> //這裡也不一樣,上面有說明
{
private:
std::string a; //這裡也不一樣,內部成員可以隨意的宣告,因為這是一個完全不一樣的類:Pair<const char*, const char*>;
std::string b;
int c; //新增加的成員,可以隨意增加減少.
public:
Pair(){ std::cout << "Im const char*" << std::endl;}
Pair(const char *p1, const char *p2):a(p1),b(p2)
{
std::cout << "Pair(const char *p1, const char *p2)" << std::endl;
}
void compare() const
{
if(a >= b)
std::cout << "oops , a is bigger:" << a << std::endl;
else
std::cout << "b is bigger:" << b << std::endl;
}
};
//main.cpp
/*
現在這個Pair<const char*, const char*> 使用的是自己寫的完全具體化(特化)的類,
可以正常使用;
再次強調把 Pair<const char*,const char*> 看成一個整體,那麼長一堆就是一個類名罷了;
此時編譯器將不會使用通用模版類來生成程式碼.因為咱自己提供了一個例項;
*/
Pair<const char*, const char*> p2("aaa","b"); //此時編譯器將不再通過通用模版類來生成了;
p2.compare();
下面說一下部分特化:
請注意部分特化與全特化的區別:
上面的全特化就是一個類的例項化,而部分特化還是一個模版;
原因是部分特化並沒有產生一個例項,還有至少一個型別引數需要外部傳入才能生成例項,這種方式與通用模版類似;
部分特化唯二需要注意的是:
1.還是一個模版;
2.與通用模版的引數數量匹配
一般把上面的特化稱為完全特化: 因為所有的型別引數已經全部確定了. 所以使用模版頭 template<> ,意思是不需要新增
額外的型別引數資訊了.
程式碼說明部分特化:
/*
還是拿上面的template<typename T, typename U> class Pair 作為例子;
現在假設已經確定第一個型別引數 T 為double 型別. 即:Pair<double , 未知的型別引數????>
這種稱為部分特化.
還是繼續強調一下,成員變數不需要與通用模版的一模一樣. 請隨意.
因為他們是不同的類,把Pair<Type1,Type2> 這個整體看成一個類;他們字首一致而已;
*/
template <typename T> //與通用模版相比,少了一個型別引數
class Pair<double , T > //其中一個引數確定為double , 另一個由外部傳入
{
T some_member;
public:
Pair(const T& t):some_member(t)
{
std::cout << "Im Partial !!! " << t << endl;
}
};
/*
main.cpp;
p3 , p4 分別由編譯器生成了2個類 Pair<double,string> Pair<double,int>;
需要注意的是:
以下2個類也可以由通用模版生成,
但是當有多個模版提供的時候,編譯器將選擇具體化匹配最高的那個.
因此,咱自己寫的部分特化的模版最為匹配.因為第一個引數是double,
此時編譯器將選擇我們的部分特化的模版
*/
Pair<double,string> p3("123");
Pair<double,int> p4(1);
部分特化的另一種方式,對指標或引用型別的部分特化:
/*
部分特化的又一種形式.
對指標或者引用型別進行部分特化.
記住部分特化也是一個模版,只有全特化才是例項;
*/
//一個通用的類模版
template <typename T>
class AA
{
T item;
public:
AA(){
std::cout << "AA :" << typeid(item).name() << std::endl;
}
};
//部分特化的類模版.
template <typename T> //由於部分特化還是一個模版,所以還是這個模版頭,與原通用模版一樣
//這裡<>內是指標型別 : T* . 意思是這個部分特化更匹配指標型別
class AA<T*>
{
T* item;
T item2;
public:
AA(){
std::cout << "AA* :" << typeid(item).name() << std::endl;
}
};
//main.cpp
AA<int> ai; //使用通用模版, T是int
//使用部分特化的class AA<T*>; T 是 int; 如果沒有部分特化的版本,那麼通用模版的T是int*;
AA<int*> aio;
特化某一個或多個成員函式 而不是類:
如果當我僅僅需要修改一個函式 ,其他的全由模版承包的時候,那麼可以這樣做:
//模版類
template <typename T>
class Spec_Func_Class
{
T item;
public:
//俺偏偏要 T 是 int 的時候對這個函式做修改
void instance_function_only(){
std::cout << "instance_function_only :" << typeid(item).name() << std::endl;
}
void show(){ std::cout << "not changed" << std::endl;}
};
//例項化這個函式
template <> //注意模版頭,跟普通特化沒啥區別
void Spec_Func_Class<int>::instance_function_only() //注意<int> ,僅僅對<int>例項化
{
std::cout << "T is int " << std::endl;
}
int main(int arg,char**arvs)
{
/*
僅僅對Spec_Func_class<int> 的一個成員函式例項化,
其他還使用模版來生成程式碼
*/
Spec_Func_Class<char> sc; //生成了class Spec_Func_class<char>;
sc.instance_function_only();
Spec_Func_Class<int> si; //生成了class Spec_Func_class<int>;
si.instance_function_only(); //使用了我們自己例項化的
return 0;
}
說明一下特化的寫法:
具體化(特化)模版的寫法核心思想就是 : 能夠匹配通用模版類.
例如:
通用模版:
template <typename T,typename U>
class A
{};
全部特化:
template <> //空 , 2個引數型別都確定了
class A<int,int>
{};
部分特化:
template <typename T> //其中一個引數型別確定了
class A<int,T> //還有一個不確定,由外部傳入;
{};
需要理解的一個核心是:
通用模版最後由編譯器生成的類一定是 : class A<XX,YY> 這樣的.
這也就是為什麼特化的類一定是 class A 後面帶<> ,同時也要理解此時 A 是一個模版;
注意以上2個特化都是對第一個通用模版類進行的特化. 特化一定是對某一個模版進行特化的;
宣告式的例項化一個類模版(被忽略的一個點):
1.首先不論是類模版還是函式模版 ,都是在被使用 時才會例項化,這個規則說明了一點,
如果模版類或函式在多個檔案中一起使用時將很可能產生多個相同的例項;
宣告方式很簡單 ,就像宣告一個類一樣,在前面加template 即可;
*2.還有在學這個宣告式例項化模版類的時候,需要注意,之前在特化的時候說了一個本質的問題是:
特化就是一個例項化;
因此把2個問題合在一起或許比較合適;
//template_A.h
//通用模版類
template <typename T,typename U>
class A
{
public:
A(){std::cout << "A" << std::endl;}
};
//部分特化
template <typename U>
class A<double,U>{
public:
A(){std::cout << "class A<int,U>"<< std::endl;}
};
//全特化
template <>
class A<int,int>
{
public:
A(){std::cout << "class A<int,int>" << std::endl;}
};
// 宣告式的例項化. 告訴編譯器去例項化一個 class A<double,int>;
template class A<double , int>;
/*
main.cpp
拿上面的class A 模版作為例子.
類模版例項化類的方式通常是 A<double,string> ads;
另外還有一種方式是宣告式例項化 例如:template class A<double,char>;
*/
#include "template_A.h" //模版標頭檔案
/*
1.
宣告式例項化,此時編譯器會乖乖的給你生成一個
template <>
class A<string,double>
{};
這樣一個類;
要理解class A是一個模版. 因此不可以template class A;
*/
template class A<string,double>; //就像一個類宣告一樣,在前面加template 即可
/*
2.
之前說明了一個特化就是一個例項化.
特化版本的 template<> class A<int,int> 已經被例項化了;
前面加extern , 意思是這個類已經例項化了,這裡直接拿來用就得了;
*/
extern template class A<int,int>;
/*
3.
上面的標頭檔案中已經例項化了一個class A<double ,int>;
與特化版本的一樣,前面加extern;
*/
extern template class A<double,int>;
int main(int a, char **args)
{
//編譯器看到這裡將生成一個class A<int,double> 類;這是之前所有例子的例項化類的方式
A<int,double> aid;
return 0;
}
下面說一下模版本身也可以作為模版引數, 唉,這話太繞了,看程式碼吧;
//把模版本身作為模版引數
/*
把本篇最上面的Stack當作引數;
< >中間的相當於宣告1個模版類 :
template <typenameT>
class SomeClass
引數只能使用模版;
這樣SuperClass的成員相當於: Stack<int> s1 ; Stack<double> s2;
*/
template <template <typename T> class templateClass > //把<>內的單獨拿出來就是聲明瞭一個模版類
class SuperClass
{
templateClass<int> s1; //例如:Stack<int>
templateClass<double> s2;
public:
SuperClass(){}
bool push(int a, double d){return s1.push(a) && s2.push(d);} //push與pop 都是模版類引數提供的
bool pop(int& a, double& d){return s1.pop(a) && s2.pop(d);}
};
//可以用於類似例項化1個 SuperClass<Stack> ss;
//main.cpp
SuperClass<Stack> ss; //<> 中間只能使用模版類
int ar[3] = {1,2,3};
double dr[3] = {4.0,5.0,6.0};
//push
for(int i = 0 ; i < 3 ; ++i)
ss.push(ar[i],dr[i]);
//pop
int pop_a;
double pop_d;
while(ss.pop(pop_a, pop_d))
cout << pop_a << ',' << pop_d << endl;
/*
下面來個複雜的, 混合使用;
第一個引數是模版類, 後面的是型別引數
*/
template <template <typename T> class TemplateClass , typename Type1, typename Type2>
class SuperClass
{
TemplateClass<Type1> s1;
TemplateClass<Type2> s2;
public:
SuperClass(){}
bool push(int a, double d){return s1.push(a) && s2.push(d);}
bool pop(int& a, double& d){return s1.pop(a) && s2.pop(d);}
};
//main.cpp
//TemplateClass = Stack, Type1 = int, Type2 = double;
SuperClass<Stack,int,double> ss;
int ar[3] = {1,2,3};
double dr[3] = {4.0,5.0,6.0};
//push
for(int i = 0 ; i < 3 ; ++i)
ss.push(ar[i],dr[i]);
//pop
int pop_a;
double pop_d;
while(ss.pop(pop_a, pop_d))
cout << pop_a << ',' << pop_d << endl;
接下來說一下友元和模版:
//前置宣告模板類
template <typename T> class FriendClass;
//模版函式
template <typename T>
void global_func(const FriendClass<T>& f) //引數需要用到前置宣告
{
std::cout << f.item << std::endl;
}
/*
模板類;
含有一個友元函式global_func;
*/
template <typename T>
class FriendClass
{
T item;
public:
FriendClass(const T& t):item(t) {}
/*
引數能否是FriendClass? 不可以!!!
FriendClass 是一個模版, 後面需要加上<T>具體型別;
global_func 後面加上了<> . 表示例項化的一個函式,而不是函式模版
意思是global_func<T> 這個函式是友元
*/
//第一種方式:
friend void global_func<>(const FriendClass<T>&);
/*第二種:
注意typename 後面的U,V . 別和T搞混了. 除非此模版使用的引數也有T;
這種方式跟宣告一個函式模版一樣;
*/
template <typename U,typename V>
friend void global_func2(const U& , const V&);
};
//第二中友元模版
template <typename U, typename V>
void global_func2(const U& a , const V& b)
{
std::cout << a.item <<',' << b.item << std::endl;
}
//main.cpp
FriendClass<int> fi(1); // 生成了class FriendClass<int>;
global_func(fi);
//生成了class FriendClass<double> 與 class FriendClass<string>;
global_func2(FriendClass<double>(19.2),FriendClass<string>("hello"));
最後,不論是通過類模版還是部分特化生成的類定義(例項化),跟全特化的類定義沒區別
例如:
//通用模版
template <typename T, typename U>
class A
{};
//部分特化的類模版
template <typename U>
class A<char, U>
{};
//全特化
template <>
class A<double,double>
{};
//main.cpp
/*
不論是通過通用模版還是部分特化的類模版例項化
的一個類定義實際跟我們自己定義的全特化是一樣的.
所以在最上面才說 特化的本質就是例項化
*/
A<char,char> acc;//通過部分特化例項化了一個 template<> class A<char,char> {};
A<double,double> add; //使用的我們自己定義的全特化類定義
A<int,double> aid; //通過通用模版類例項化了 template<> class A<int,double>{};