面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于,OOP能处理类型在程序运行之前都未知的情况;而在泛型编程中,在编译时就能获知类型了。
STL中典型的容器、迭代器和算法都是泛型编程的例子。模板是泛型编程的基础。
函数模板
先说明下函数重载,重载就是函数名相同但实参的类型或数量不同的函数的调用。
注意有两种:实参是类型不同或数量不同。(为什么不说形参,因为会有默认值的存在)
如得到一数组中最大值,因为有int和double基本的类型,一个函数无法表达。
这样函数重载中,实现的功能是一样的,但是类型不同。
我们可以通过定义一个函数模板,而不是对每一个类型都定义一个自己的版本。
template<class T>或template<typename T>
return-type sort(...T...)
一个函数模板可以有多个模板参数,也可以有多个基本数据类型,其他的类说明
template<typename T1, typename T2>
void print(T1 arg1, T2 arg2, string s, int k)
{
cout <<arg1<<s<<arg2<<k<<endl; return ;
}
类模版的定义和声明都以关键字template开头,后面接上以逗号分隔的模版参数表(template parameter list),模版参数表以<>括起来,模版参数一种是类型参数(type parameter),另一种是非类型参数(nontype parameter)。关于类型参数模版不多说了,有关键字class或typename接上后面的标识符构成,例如template template ,对于非类型参数模版(nontype parameter)需要注意的是它由普通参数声明构成的,它代表类模版中的一个常量,例如定义template
tmplate<unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M]){
return strcmp(p1,p2);
}
但我们调用compare(“hi”,”mom”);时,编译器会用字面值常量的大小来代替N和M,从而实例化模板因此编译器会实例化出如下版本
int compare(const char (&p1)[3], const char (&p2)[4])
一个非类型参数可以是一个整型,或者是一个指向对象或是函数类型的指针或引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参(上面的数组的引用)必须具有静态的生命周期。我们不能用一个普通的(非static)局部变量或动态对象(new出来的)作为指针或引用非类型模板参数的实参。指针参数也可以用nullptr或一个值为0的常量表达式来实例化。
简单来说,整型的非类型模板参数可以当做是全局变量来理解
下面来解释下为什么要使用数组的引用而不是数组
template<class T,int N>
void print(T (&r)[N])
{
cout<<sizeof(r)<<endl;
for (int i=0;i<N;++i)
{
cout<<r[i]<<",";
}
cout<<endl;
}
template<class T,int N>
void print1(T r[N])
{
cout<<sizeof(r)<<endl;
for (int i=0;i<N;++i)
{
cout<<r[i]<<",";
}
cout<<endl;
}
int main()
{
int ss[] = {1,2,3,4,5,6,7,8,9};
print(ss);
print1<int,9>(ss);
return 0;
}
原因也很简单,传数组名直接给模板时会被当做指针来用,编译器不知道数组的维数,传数组的引用时,编译器则可以推导出数组的维数也就知道了数组的大小了。
模板编译
编译器遇到一个模板定义时,它并不生成代码。只有但我们实例化模板的一个特定版本时,编译器才会生成代码。
当我们调用一个函数时,编译器只需要知道函数的声明。模板则不同,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常包括声明也包括定义。