liitdar 2018-06-05 20:51:03 2789 收藏 23
分类专栏: C/C++语言 文章标签: C++ 模板
C++是一门强类型语言,所以无法做到像一些动态语言(如 python 、 javascript )那样:编写出一段通用的逻辑,然后把任意类型的变量传入进行处理。不过,泛型编程弥补了C++的这个缺点,通过把通用逻辑设计为模板,摆脱了类型的限制,提供了继承机制以外的另一种抽象机制,极大地提升了代码的可重用性。
模板是泛型编程的基础,泛型编程是一种代码编写方式,通过使用泛型编程,我们可以编写出独立于任何特定类型的代码。
模板是创建泛型函数或类的蓝图(公式)。C++的STL容器,及其迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 vector ,我们可以定义许多包含不同类型元素的 vector ,比如: vector
C++的模板包括函数模板和类模板。
把处理不同类型的公共逻辑抽象成函数,就得到了函数模板。函数模板可以用来创建一个通用的函数,以支持多种不同类型的形参,避免重载函数的函数体重复设计。
函数模板的最大特点是把函数使用的数据类型作为参数。
template <typename type>
ret-type func-name(parameter list)
{
// 函数的主体
}
说明:
type 是函数所使用的数据类型的占位符名称,这个占位符名称可以在函数定义中使用;
关键字"typename"可替换为"class",两者作用一样;
ret-type 为函数的返回值;
模板函数的示例代码(template_fun.cpp)如下:
#include
using namespace std;
template <typename T>
T Max(T a, T b){ return (a > b ? a : b);}
int main()
{
int i = 1;
int j = 2;
cout << "Max(i, j) is: " << Max(i, j) << endl;
cout << "Max(i, j) is: " << Max<int>(i, j) << endl;
float x = 1.1;
float y = 2.2;
cout << "Max(x, y) is: " << Max(x, y) << endl;
cout << "Max(i, j) is: " << Max<int>(i, j) << endl;//显示指定的话,会先转化为int类型
return 0;
}
编译并运行上述代码,结果如下:
在上述结果能够看到,通过使用函数模板,我们可以使函数Max支持多种类型( int 和 float )的参数比较,实现了泛型编程的效果。
在前面的函数模板的代码示例中,我们使用了两种方式为函数模板指定类型参数,如下:
cout << "Max(i, j) is: " << Max(i, j) << endl;
cout << "Max
正常情况下,我们使用"Max
使用类模板可以使用户为类声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取值为任意类型。
注意:模板的声明或定义只能在全局、命名空间或类范围内进行,不能在局部范围、函数内进行,比如不能在main函数中声明或定义一个模板。
template <typename type>
class class-name {
// 类的主体
}
说明:
type 是类所使用的数据类型的占位符名称,这个占位符名称可以在类的定义中使用;
关键字"typename"可替换为"class",两者作用一样;
类模板的类声明代码(template_class.h)如下:
#ifndef __TEMPLATE_CLASS_H__
#define __TEMPLATE_CLASS_H__
template <typename T>
class CTmpl
{
public:
// 成员函数声明
T FunA(T a, T b);
CTmpl();
};
#endif
//类模板的类实现代码(template_class.cpp)如下:
#include
#include "template_class.h"
using namespace std;
template <typename T>
CTmpl<T>::CTmpl(){}
// 成员函数的具体实现
template <typename T>
T CTmpl<T>::FunA(T a, T b){ return (a + b);}
int main()
{
CTmpl<int> tmpl_int;
cout << "tmpl_int.FunA(1, 2) is: " << tmpl_int.FunA(1, 2) << endl;
CTmpl<float> tmpl_float;
cout << "tmpl_float.FunA(1.1, 2.2) is: " << tmpl_float.FunA(1.1, 2.2) << endl;
return 0;
}
编译并运行上述代码,结果如下:
在上述结果能够看到,通过使用类模板,我们可以使类CTmpl的数据成员和成员函数支持多种类型( int 和 float ),实现了泛型编程的效果。
在这里需要注意在类模板外部定义成员函数的方法,如下:
template <typename T>
ret-type class-name<typename T>::fun-name(parameter list)
{
// 函数的主体
}
可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。
类模板的类型形参默认值形式为:template
类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如template
在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。比如template
普通类继承模板类比较简单,如:
template<class T>
class TBase{
T data;
……
};
class Derived:public TBase<int>{
……
};
模板类继承普通类:
class TBase{
……
};
template<class T>
class TDerived:public TBase{
T data;
……
};
类模板继承类模板:
template<class T>
class TBase{
T data1;
……
};
template<class T1,class T2>
class TDerived:public TBase<T1>{
T2 data2;
……
};
模板类继承模板参数给出的基类------继承哪个基类由模板参数决定
#include
using namespace std;
class BaseA{
public:
BaseA(){cout<<"BaseA founed"<<endl;}
};
class BaseB{
public:
BaseB(){cout<<"BaseB founed"<<endl;}
};
template<typename T, int rows>
class BaseC{
private:
T data;
public:
BaseC():data(rows){
cout<<"BaseC founed "<< data << endl;}
};
template<class T>
class Derived:public T{
public:
Derived():T(){cout<<"Derived founed"<<endl;}
};
void main()
{
Derived<BaseA> x;// BaseA作为基类
Derived<BaseB> y;// BaseB作为基类
Derived<BaseC<int, 3> > z; // BaseC作为基类
}
非类型模板形参:模板的非类型形参也就是内置类型形参,如template
注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template
非类型模板形参的形参和实参间所允许的转换:
(1) **允许从数组到指针,从函数到指针的转换。即数组到指针的转换。**如:
template <int *a>
class A{};
int b[1];
A<b> m;
(2) **const修饰符的转换。即从int *到const int *的转换。**如:
template<const int *a>
class A{};
int b;
A<&b> m;
(3) **提升转换。**即从short到int 的提升转换。如:
templatee<int a>
class A{};
const short b=2;
A<b> m;
(4) **整值转换。**即从int 到unsigned int的转换。如:
template\<unsigned int a>
class A{};
A<3> m;
(5) 常规转换。
总结一下,C++只有模板显式实例化(explicit instantiation),隐式实例化(implicit instantiation),特化(specialization,也译作具体化,偏特化)。首先考虑如下模板函数代码:
template <typename T>
void swap(T &a, T &b){
...
}
我们知道,模板函数不是真正的函数定义,他只是如其名提供一个模板,模板只有在运行时才会生成相应的实例,隐式实例化就是这种情况:
int main(){
....
swap<int>(a,b);
....
}
它会在运行到这里的时候才生成相应的实例,很显然的影响效率。
这里顺便提一下swap
前面已经提到隐式实例化可能影响效率,所以需要提高效率的显式实例化,显式实例化在编译期间就会生成实例,方法如下:
template void swap<int>(int &a,int &b);
这样就不会影响运行时的效率,但编译时间随之增加。
这个swap可以处理一些基本类型如long int double,但是如果想处理用户自定义的类型就不行了,特化就是为了解决这个问题而出现的:
template <> void swap<job>(job a,job b){...}
其中job是用户定义的类型。
测试代码如下:
#include
using namespace std;
template<typename T1,typename T2>
class Test{
public:
Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<<endl;}
private:
T1 a;
T2 b;
};
template<> //全特化,由于是全特化,参数都指定了,参数列表故为空。
class Test<int ,char>{
public:
Test(int i,char j):a(i),b(j){cout<<"全特化"<<endl;}
private:
int a;
char b;
};
template<typename T2> //由于只指定了一部分参数,剩下的未指定的需在参数列表中,否则报错。
class Test<char,T2>{
public:
Test(char i,T2 j):a(j),b(j){cout<<"个数偏特化"<<endl;}
private:
char a;
T2 b;
};
template<typename T1,typename T2> //这是范围上的偏特化
class Test<T1*,T2*>{
public:
Test(T1* i,T2* j):a(i),b(j){cout<<"指针偏特化"<<endl;}
private:
T1* a;
T2* b;
};
template<typename T1,typename T2>//同理这也是范围上的偏特化
class Test<T1 const,T2 const>{
public:
Test(T1 i,T2 j):a(i),b(j){cout<<"const偏特化"<<endl;}
private:
T1 a;
T2 b;
};
int main()
{
int a;
Test<double,double> t1(0.1,0.2);
Test<int,char> t2(1,'A');
Test<char,bool> t3('A',true);
Test<int*,int*> t4(&a,&a);
Test<const int,const int> t5(1,2);
return 0;
}
而对于函数模板,却只有全特化,不能偏特化:
#include
using namespace std;
//模板函数
template<typename T1,typename T2>
void fun(T1 a,T2 b){
cout<<"模板函数"<<endl;
}
//全特化
template<>
void fun(int a,char b){
cout<<"全特化"<<endl;
}
//函数不存在偏特化,以下代码是错误的
/*
template
void fun(char a,T2 b){
cout<<"偏特化"<
int main()
{
int a=0;
char b='A';
fun(a,a);
fun(a,b);
return 0;
}
在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。
(1) 声明一个类模板的指针和引用,不会引起类模板的实例化,因为没有必要知道该类的定义。
(2) 定义一个类类型的对象时需要该类的定义,因此类模板会被实例化。
(3) 在使用sizeof()时,它是计算对象的大小,编译器必须根据类型将其实例化出来,所以类模板被实例化。
(4) new表达式要求类模板被实例化。
(5) 引用类模板的成员会导致类模板被编译器实例化。
(6) 需要注意的是,类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址的时候,才被实例化。用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型。