提前说明一下:
写类模版时把 <类型参数 >与 类名 看成一个整体 , 比较好理解;
比如 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>{};