一、模板参数再介绍
初级模板知识
模板参数是一个用来存放类型名称(int double
等内置类型和自定义类型名称)的变量。在代码实现中使用模板参数写代码(写一个函数或类),会增加代码复用的能力。
写出的函数或类被称为函数模板和类模板。 模板实例化:编译器根据使用模板时指定的类型名称,用这个类型名称来替代模板参数,生成相应的函数或类。
使用函数模板时,通过传入参数类型实例化出相应的模板。 而使用类模板时,需要显示说明模板参数类型,才能实例化出相应的类。
二、非类型模板参数
前面提及的模板参数接受的都是类型的名字,还有一种模板参数接受的是一个常数。
看下面一段代码:
#include<iostream>template<classT,size_t size=10>classarr{private:T arr[size];intlen=size;public:arr();~arr();T&operator[](constarr&a);boolempty();};这个是C++STL中对数组的改造array的模拟实现(未完成),与c风格数组相比,容器arr对越界访问更加严格。我们看到arr是一个类模板,有两个模板参数,第二个模板参数就是上面提及的非类型模板参数,相当于C语言中常量的宏定义。
但与宏定义相比较,这样的类模板可以根据传入参数的不同自动调整常量,不需要改变代码,但是宏定义恰恰相反。
举个arr的使用例子来体会非类型模板参数的用途:
#include<iostream>usingnamespacestd;#include<array>intmain(){array<int,20>arr={1,2,3,4,5};for(inti=0;i<arr.size();i++){cout<<arr[i]<<" ";//1 2 3 4 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0}cout<<endl;return0;}三、函数模板的特化
为什么需要引入特化
就函数模板而言:由于所有符合函数模板的参数列表中的每组参数并不都要根据函数模板实例化出的函数进行运算,总有一两个特例要实现不同的方法,所以引入模板特化。比如我想实现一个函数,当两个参数是一切参数时,判断第一个参数是否大于第二个参数。如果这样实现:
#include<iostream>usingnamespacestd;template<classT>boolfun(T a,T b){returna>b}intmain(){inta=10,b=20;int*pb=&b,*pa=&a;cout<<fun(a,b)<<endl;cout<<fun(pa,pb)<<endl;}存在指针比较,是不是没有任何意义,当参数为int*时,我们特化一个函数模板,让它返回指针所指向的最大的数:
#include<iostream>usingnamespacestd;template<classT>boolfun(T a,T b){returna>b}template<>boolfun<int*>(int*a,int*b){return*a>*b;}intmain(){inta=10,b=20;int*pb=&b,*pa=&a;cout<<fun(a,b)<<endl;cout<<fun(pa,pb)<<endl;}函数模板特化的语法:
1.前提是有一个相关的函数模板
2.在特化函数模板之前加上template<>
3.在特化声明函数时在函数名之后参数表之前加上<特化的类型名称>
4.要求函数的其余部分除将模板参数特化替代外,必须和原来函数一模一样否则,就会编译报错。
函数模板特化的特点:
由于函数模板的性质,当定义一个将函数模板中所有模板参数替代成统一类型名称的函数fun时,调用一个既可以走函数模板,又可以直接调用fun时,编译器会不再实例化函数模板,直接调用fun。因此函数模板特化的作用不大,大多数情况都可以通过定义一个将函数模板中所有模板参数替代成统一类型名称的函数fun,调用fun直接解决问题,但也不排除只有使用函数模板特化的方法才能解决问题的项目。
使用上面提及的第二种常规方法解决之前需要函数模板特化才可以解决的问题:
#include<iostream>usingnamespacestd;template<classT>boolfun(T a,T b){returna>b;}boolfun(int*a,int*b){return*a>*b;}intmain(){inta=10,b=20;int*pb=&b,*pa=&a;cout<<fun(a,b)<<endl;cout<<fun(pa,pb)<<endl;}四、类模板的特化
1.全特化
顾名思义就是将所有参数都特化,和函数模板特化一样,这里也强调一下:函数模板特化的方式只有一种,类模板特化可以全特化,也可以半特化。
示例代码:
#include<iostream>usingnamespacestd;template<classT1,classT2>classc1{private:T1 t1;T2 t2;public:c1(){cout<<"T1,T2"<<endl;}};template<>classc1<int,int>{private:intt1;intt2;public:c1(){cout<<"int ,int "<<endl;}};intmain(){c1<int,double>cc;c1<int,int>c1;//T1,T2//int, int}和函数模板特化一致,特化定义前要加上template<>
特化声明时l类的名字和{}之间要加上<特化的类型名称>
2.半特化(别称:偏特化)
先来看一下半特化的代码演示:
#include<iostream>usingnamespacestd;template<classT1,classT2>classc1{private:T1 t1;T2 t2;public:c1(){cout<<"T1,T2"<<endl;}};template<classT1>classc1<T1,int>{private:T1 t1;intt2;public:c1(){cout<<"T1 ,int "<<endl;}};intmain(){c1<int,double>cc;c1<int,int>c1;//T1,T2//T1, int}半特化和全特化的不同在于:半特化定义之前,加的是template<未特化的模板参数声明>特化时类名称和{}之间加的是<未特化的模板参数,特化的模板参数>(这个排序要求和类模板定义之前的模板参数声明要一一对应下面的例子就是最好的说明)
#include<iostream>usingnamespacestd;template<classT1,classT2,classT3>classc1{private:T1 t1;T2 t2;T3 t3;public:c1(){cout<<"T1,T2,T3"<<endl;}};template<classT1,classT3>classc1<T1,int,T3>{private:T1 t1;intt2;public:c1(){cout<<"T1 ,int,T3 "<<endl;}};intmain(){c1<int,double,int>cc;c1<int,int,double>c1;/*T1, T2, T3 T1, int, T3*/}半特化还有一种特殊的例子,当被特化的类未指针或引用时:
template<classT1,classT2>classData{public:Data(){cout<<"Data<T1, T2>"<<endl;}private:T1 _d1;T2 _d2;};//两个参数偏特化为指针类型template<typenameT1,typenameT2>classData<T1*,T2*>{public:Data(){cout<<"Data<T1*, T2*>"<<endl;}private:T1 _d1;T2 _d2;};//两个参数偏特化为引用类型template<typenameT1,typenameT2>classData<T1&,T2&>{public:Data(constT1&d1,constT2&d2):_d1(d1),_d2(d2){cout<<"Data<T1&, T2&>"<<endl;}private:constT1&_d1;constT2&_d2;};voidtest2(){Data<int,double>d2;// 调用基础的模板Data<int*,int*>d3;// 调用特化的指针版本Data<int&,int&>d4(1,2);// 调用特化的指针版本}