模板是C++泛型编程的基础,一个模板就是创建类或者函数的蓝图或者说公式。一个类模板或函数模板可以给程序省去多余的代码,以实现代码的复用。
一. 函数模板
假如我们想写一个比较大小的函数,我们先完成一个整型的比较,又想完成浮点数等的比较,可能很多人会想到用函数重载。这是可行的,但是在重载函数的过程中,实现的代码是一致的,就是函数的参数列表不同而已,这样写出来的代码重复太多,代码的复用率低。
我们可以利用函数模板解决这个问题:例如compare 的模板函数
template <class T>
int compare(const T& v1,const T& v2)
{
if ( v1<v2)
return -1;
if (v1>v2)
return 1;
return 0;
}
模板定义以关键字template开始,后跟一个模板参数列表(template parameter list):
template <class T> //class 可以替换为typename,两者无区别
<span style="color:#4f4f4f">模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们指定模板实参,将其绑定到模板参数上。</span>
实例化函数模板
当我们调用一个函数模板时,编译器用函数实参为我们推断模板实参,即我们调用compare 时,编译器使用实参的类型来确定绑定到模板参数 T 的类型。
例如:在下面的调用中:
cout << compare(1,0)<<endl;//T为int
模板类型参数和非模板类型参数
我们可以将模板类型参数看做类型说明符,比如 内置类型的int 或者类类型等。我们可以将T看作一种类型,它可以作为函数的返回值,也可以作为函数的参数类型。
template <class T>
除了定义模板参数,我们也可以定义非类型参数(nontype parameter)。一个非类型参数表示一个值而非类型。
template <int N,int M>
int compare ( const char (&p1) [N] ,const char (&p2) [M])
{
return strcmp(p1,p2);
}
在上面的函数中,当在模板中定义非类型参数时,在程序运行时,编译器会推断出参数的值,,并用值来代替参数,这些值必须是常量表达式。
我们将自己的参数定义为数组的引用,并且利用非模板参数定义了数组的大小,这样也可以验证N和M是否是常量表达式。
我们如下调用函数时,编译器会实例化如下版本
compare (“hi”,“mom")
int compare ( const char (&p1) [2] ,const char (&p2) [3])
inline 和 constexpr的函数模板
函数模板可以声明为inline 或constexpr的,和非模板函数一样,inline 或者 constexpr 说明符放在模板参数列表之后,函数返回类型之前,类型如下:
template <typename T> inline T min(const T&v1,const T&v2);
二. 类模板
类似函数模板,类模板以关键字template开始,后跟模板参数列表。类型如下:
template <class T>
class 类名{ };
实例化类模板
在程序运行时,编译器会根据类的实例化来推断类模板的T类型。
Blob<string> names;
Blob<double> prices;
names的定义创建了一个Blob类,每个T类型都可被string替换;prices的定义创建了一个Blob类,每个T类型都可以被double类型替换。
类模板和友元
当一个类包含一个友元函数声明时,类与友元各自是否是模板相互是无关的。如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例;如果友元自身是模板,类可以授权所有模板实例,也可以只授权特定实例。
类模板的static成员
与任何其他类相同,类模板可以声明static成员。
template <typename T> class Foo{
static std::size_t count()
{ return ctr;}
private:
static std:: size_t ctr;
}
对于上面这段代码,给定任意的类型T,都会有一个Foo<T> :: ctr 和Foo <T> :: count 成员,所有的Foo<T>类型的对象共享相同的ctr 和 count 函数。
与非模板类的静态成员函数或者变量相同,模板类的static 成员必须有且仅有一个定义,但是,每个类模板实例都有一个独立的static 变量或函数。
在类外定义并初始化static成员变量:
template <class T>
size_t Foo<T>:: ctr = 0;
与非类模板的静态成员相同,我们可以通过类类型对象来访问一个类模板的static成员,也可以使用域运算符直接访问成员:
Foo<int> f;
f.count();
Foo<int> :: count();
最后再看一个小知识:
如果我们希望使用一个模板类型参数的类型成员,就必须告诉编译器该名字是一个类型,我们需要使用typename关键字:
template <class T>
typename T:: value_type top(const T& v){ }
简而言之:如果想要在template中涉及一个嵌套从属类型名时,需用关键字 typename 告诉编译器该名字表示一个类型。