C++模板简介
标签: c++ template
对于C++模板,之前很少使用,这里整理下,以备后忘。
1. 什么是模板
先来看下面2个加法函数:
int sum(int a, int b)
{
return a + b;
}
double sum(double a, double b)
{
return a + b;
}
上面的2个函数,虽然它们的返回值和参数类型不一致,但函数名和函数体完全一致。如果能提炼出一个通用函数,并适用不同的数据类型,则会大大提高代码的可重用性。C++的模板就可以解决这种问题。
模板是C++支持参数化多态的工具,它可以实现类型参数化,即使类中的数据成员或成员函数的参数、返回值取得任意类型。模板是一种对类型进行参数化的工具,能减少程序员编写类型无关的代码。
C++的模板可分为函数模板和类模板,通过函数模板具体化的函数称为模板函数,通过类模板具体化的类称为模板类。
2.函数模板
所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板
1.1函数模板的定义和使用
定义一个函数模板的格式并不复杂,形式如下:
template <typename 形参名,...>
返回值 函数名(函数参数列表)
{
//func body
}
其中,template是声明模板的关键字;typename用来标识的形参名用来表示一个虚拟类型,当使用函数模板时,会将其具体化为具体的类型,typename关键字也可以用class替换。
下面,对刚开始的2个函数,使用函数模板定义并调用
#include <iostream>
template <typename T>
T sum(T a, T b)
{
return a + b;
}
int main()
{
int a=10,b=10;
std::cout<<sum(a,b)<<std::endl; //相当于调用 int sum(int,int)
double x=10.2,y=10.3;
std::cout<<sum(x,y)<<std::endl; //相当于调用 double sum(double,double)
return 0;
}
它是如何兼用int和double类型的呢:
上面编写的 T sum(T a, T b)
,只是一个“函数模板”,使用它是需要先实例化为一个“模板函数”(如 int sum(int,int)
)。编译器在编译阶段,为每个调用次模板的代码生成了一个函数。
- 函数模板并不是函数,,它仅在编译时根据调用此模板函数时传递的实参类型来生成具体的函数体!若函数模板没有被调用责不生成任何代码也不对模板代码作任何语法检查。
模板的形参有类型形参和非类型形参:
- 类型形参:由关键字typename加说明符构成,表示一种未知的类型,上面例子中的形参都是类型形参。
- 非类型形参:非类型形参是内置类型形参,如template <int M,int N>
,其中int M
是模板非类型形参。
a)非类型形参只可以是整型、指针或引用;
b)绑定到整形(非类型参数)的实参必须是一个常量表达式,绑定到指针或引用(非类型参数)的实参必须具有静态的生存期(比如全局变量),即在编译阶段就能确定其值。
1.2函数模板的重载
函数模板的重载也称之为函数的特例化。
看下面一个例子:
template <typename T>
void fun(T a)
{
cout<<"template fun: "<<a<<endl;
}
void fun(int a)
{
cout<<"int fun: "<<a<<endl;
}
int main()
{
fun('a');
fun(2);
return 0;
}
输出结果是:
template fun: a
int fun: 2
- 一般是在定义的函数模板无法满足所有的情况时,对特定的类型进行特例化。
- 如果一个调用,即匹配普通函数,又能匹配模板函数的话,则优先匹配普通函数。因此,当我们模板特例化的时候,会先匹配特例化的函数。
3.类模板
相应的,类模板是用来生成类的通用模板。
3.1类模板的定义及实例化
定义一个类模板的格式如下:
template <typename 模板参数表>
class 类名
{
//...
};
编译器不能为类模板推断模板参数类型,使用时必须显式的提供模板实参。同样的,其形参可以时类型形参,也可以是非类型形参。
下面是一个类模板定义的例子
template<typename T>
class Compare
{
public:
Compare(T a, T b)
{
m_data1 = a;
m_data2 = b;
}
T max()
{
return (m_data1 > m_data2) ? m_data1 : m_data2;
}
T min()
{
return (m_data1 < m_data2) ? m_data1 : m_data2;
}
private:
T m_data1;
T m_data2;
};
上面的例子中,类模板的成员函数是定义在内部的,也可以定义在外部。定义在类模板之外的成员函数必须以关键字template
开始,后接类模板参数列表。
如上面的max
在外部定义时:
template <typename T>
T Compare<T>::max()
{
return (m_data1 > m_data2) ? m_data1 : m_data2;
}
与函数模板类似,它仅在编译时根据实例化本模板时传递的实参来生成具体的类代码!若类模板没有被实例化也没有被调用,那编译器不会为本模板生成任何代码也不对模板代码作任何语法检查。
3.2类模板的静态数据成员
对类模板的静态数据成员,有如下:
- 类模板实例化的每个模板类都有自己的类模板静态数据成员,该模板类的所有对象共享一个静态数据成员。
- 模板类的静态数据成员应在文件范围内初始化。
可通过下面例子进行验证
template <typename T>
class A
{
T m;
static T n;
public:
A(T a):m(a){n += m;}
void disp(){cout<<"m="<<m<<", n="<<n<<endl;}
};
template <typename T>
T A<T>::n = 0; //静态数据成员的初始化
int main()
{
A<int> a(2);
a.disp();
A<int> b(3);
b.disp();
A<double> c(1.2),d(4.6);
c.disp();
d.disp();
return 0;
}
3.2成员函数模板
在此前的例子中都是类模板中有成员函数,而对于普通类来说,也可以有成员函数模板,如以下代码所示:
struct normal_class
{
int value;
template<typename T>
void set(T const &v) {value = int(v);}
template<typename T>
T get();
};
template<typename T>
T normal_class::get()
{
return T(value);
}
普通类中的成员函数模板可以在类中当场实现,也可以在类外单独实现。在类外实现时,由于类不是模板,无需在类名后增加模板实参列表。
类模板的成员函数还可以有额外的模板参数,如:
template<typename T0>
struct a_class_template
{
T0 value;
template<typename T1>
void set(T1 const &v){value = T0(v);}
template<typename T1>
T1 get();
};
// 类模板的成员函数模板在类模板外的实现方法
template<typename T0> template<typename T1>
T1 a_class_template<T0>::get()
{
return T1(value);
}
3.3类模板嵌套
1.在类模板中允许再嵌入模板,因此类模板的嵌套类也是一个模板,它可以使用外围类模板的模板参数;
2.当外围类模板被实例化时,内部嵌套类模板不会被实例化,只有确实需要它的完整类类型时,它才会被实例化;
3.嵌套在类模板内部的类隐含地成为类模板。
如下示例:
template <typename T0>
class ClassA
{
public:
ClassA(const T0& val)
{
m_data = val;
}
void dispA()
{
cout<<"dipsA:"<<m_data<<endl;
}
template <typename T1>
class ClassC
{
public:
ClassC(const T0& val0, const T1& val1)
{
m_data_c0 = val0;
m_data_c1 = val1;
}
void dispC()
{
cout<<"dipsC:"<<m_data_c0<<";"<<m_data_c1<<endl;
}
private:
T0 m_data_c0; //可以使用外围类模板的参数T0
T1 m_data_c1;
};
class ClassB //嵌套在类模板的类隐含的成为模板
{
public:
ClassB(const T0& val)
{
m_datab = val;
}
void dispB()
{
cout<<"dipsB:"<<m_datab<<endl;
}
private:
T0 m_datab;
};
private:
T0 m_data;
};
int main()
{
ClassA<int> objA(1000);
objA.dispA();
ClassA<int>::ClassB objB(200);
objB.dispB();
ClassA<int>::ClassC<float> objC(30, 40.5);
objC.dispC();
return 0;
}
3.4类模板与友员的关系
简单总结,有以下几条:
1.如果一个非模板类包含一个模板友元,则所有友元实例被授权可以访问该类
如下:
class NormalClass
{
//A实例化的所有模板类都是NormalClass的友元,且无须前置声明
template <typename T> friend class A;
//用类NormalClass实例化的A是NormalClass的一个实例
friend class A<NormalClass>;
};
template <typename T>
class A
{
...
}
2.如果一个类模板包含一个非模板友元,则友元被授权可以访问所有类模板实例。
如下:
template <typename T>
class A
{
//NormalClass是一个非模板类,它是A所有实例的友元,不需要前置声明
friend class NormalClass;
};
* 3.如果一个类模板与友元模板拥有相同的类型参数,则类与友元为一对一的友好关系*
如下:
//前置声明
template <typename> class ClassA;
template <typename> class ClassB;
template <typename T>
class ClassB
{
...
//每个ClassA实例将访问权限授予与相同类型实例化的ClassB
friend class ClassA<T>;
};
//如下
ClassB<char> ca; //ClassA<char>是本对象的友元
ClassB<int> ia; //ClassA<int>是本对象的友元
4.如果一个类模板与友元模板拥有不同的类型参数,则类的每一个实例授权给所有模板实例。
如下:
template <typename T>
class ClassA {
//ClassB的所有实例都是ClassA的每个实例的友元,不需要前置声明
template <typename X> friend class ClassB;
};
3.5类模板的全特化与偏特化
通函数模板特化一样,当对于某种类型的参数,模板无法写成通用或右更好的实现时,就要对该种类型进行特化。
类模板的特化分为全特化与偏特化:全特化就是全部限定实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分
如下:
template<typename T1, typename T2>
class ClassA
{
public:
ClassA(T1 a,T2 b):x(a),y(b){cout<<"模板类"<<endl;}
private:
T1 x;
T2 y;
};
template<>
class ClassA<int , char>
{
public:
ClassA(int a, char b):x(a),y(b){cout<<"这是全特化"<<endl;}
private:
int x;
char y;
};
template <typename T2>
class ClassA<char, T2>
{
public:
Test(char a, T2 b):a(x),y(b){cout<<"这是偏特化"<<endl;}
private:
char x;
T2 y;
};
//下面3句分别调用类模板、全特化和偏特化
ClassA<float,float> t1(2.0f, 3.0f);
ClassA<int,char> t2(1,'a');
ClassA<char,int> t3('a',2);