目录
概述
泛型编程是一种编程思想,其追求摆脱数据类型的束缚,通过将数据类型作为参数的处理,提高代码的通用性。模板是泛型编程的基础,它支持将数据类型作为参数,从而使得我们在处理具有相似性的对不同数据类型的处理时大大减少类代码量。模板在定义时并不会占有内存,而是在我们使用模板时针对我们的参数具体产生一个处理此类参数的函数/类,并在此时分配内存,这个过程就叫做模板的实例化。
模板大致可以分为函数模板和类模板。两者有很多相似之处,但也略有不同。
函数模板
1.函数模板的定义
下面提供简单的函数模板定义代码:
template<class T>//此处class并不是定义类,而是标注T代表一个类型名。
//class也可以用typename替代,作用相同
void CoutInformation(T a)//此处为函数CoutInformation的形参列表。
{
cout<<a<<endl;
}
CoutInformation<int>(5);//显式
char a("666");
CoutInformation(a);//隐式
定义模板使用template关键字,与相连的<>内的是模板参数列表,可以在此处列出需要的类型,多个类型要使用多个class或者typename并用逗号隔开,但不建议class和typename混用。class和typename 可以标志T为类型参数,这里T也是自主起的来代替类型的名称,可以自主选择。编译器允许定义模板函数同名的函数,如果条件相同,编译器会优先调用非模板函数,且不会在生成此模板函数的实例化版本。
一般我们将把函数模板的定义放在.h文件中,在用include将其包含到.cpp文件中。(不需要考虑函数模板的重定义问题。)
模板函数可以写成内联函数,inline声明写于<>与函数返回类型之间。
2.函数模板的使用
当我们调用模板函数时我们可以使用尖括号明确表示我们的参数类型,也可以省略尖括号让编译器自主帮我们推断参数类型。函数参数都有了明确的类型,编译器就会实例化出针对此类型的实例化版本,当我们再次调用相同类型的参数时,编译器就会调用模板函数的实例化版本,不会再二次实例化。但我们省略模板参数列表时,我们要确保编译器可以通过我们传递的函数实参推断出模板参数列表中的内容。当我们使用<>明确模板参数的时候,要确保与函数实参的类型相匹配。
我们可以这样认为,模板函数的定义并不会让编译器生成代码,只有在我们调用函数模板时,编译器为我们实例化特定版本的函数之后,编译器才生成代码。也就是说,函数模板就像一张图纸一样,一旦我们有了图纸中的参数,我们就可以通过这些参数具体的将图纸中的内容建设出来。
3.非类型的模板参数
template<typename T,int a>//a为非类型模板参数
int CoutInformation(T b)
{
cout<<b<<endl;
return a;
}
int c=520;
int d=CoutInformation<int,100>(a);
模板参数不仅仅是类型参数,也可以是非类型参数,也就是一段数据。对于非类型参数,我们需要用具体的数据类型来修饰。这些非类型模板参数同样可以被编译器推断,如果编译器无法推断出这些非类型模板参数的值,就需要我们使用<>具体的给它提供值。同时,需要注意的是模板实例化发生在编译的过程。因此我们使用<>提供的模板参数都应该为常量表达式,否则编译器将会报错。
下面提供特殊示例:
#include<iostream>
using namespace std;
template<unsigned L1,unsigned L2>
void Coutchar(const char a[L1],const b[L2])
{
cout<<a<<"and"<<b<<endl;
}
int main()
{
Coutchar("yes","no");//此时非类型模板参数L1,L2就是编译器推断的
//但是如果我们使用<5,4>提供非类型模板参数,编译反而会报错,
//因为系统推断"yes"长度为4,"no"长度为3,传递参数与系统推断不符
}
类模板
1.函数模板的定义
template<typename T>
class Goodclassname
{
public :
void CoutInformation()
{
cout<<a<<endl;
}
private :
T a;
};
和函数模板相似,我们也可以通过类模板的实例化功能来避免代码的重复性。但需要注意的是,类模板并不能像函数模板那样推断模板参数的内容,也就是说,我们必须使用<>来明确类模板参数的内容。同时,类模板也可以在模板参数列表中设定默认参数值。
2.类模板的成员函数
如果我们将类模板的成员函数的函数体写在类模板定义的内部,和普通类成员函数一样,其也会被隐式声明为内联函数。如果我们将类模板的成员函数的函数体写在类模板定义的外部,那我们就需要标明类模板的模板参数,也就是在函数前使用template<>,同时在尖括号内将所有模板参数名列出,多个模板参数之间用逗号分隔。这是因为类模板一旦被实例化之后就会产生自己版本的成员函数,也就是说,类模板的成员函数拥有与类模板相同的模板参数。
一个类模板可以有多个成员函数。当类模板被实例化时,并不是说全部的成员函数都将被实例化。而是实例化那些被使用过的成员函数。也就是说,一个类模板的成员函数只有在使用的时候才被实例化。
template<class T>
class Goodclassname
{
public:
void CoutInformation();
private:
T a;
};
template<class T>
void Goodclassname<T>::CoutInformation()
{
cout<<a<<endl;
}
3.模板类名字的使用
在类模板定义内部我们直接使用类模板的名字并不需要带上类模板参数,因为系统默认模板类内的成员的模板参数与模板类保持一致。但是我们在类模板定义外面使用类模板名称的时候,应在类模板名字后使用<>列出模板参数,因为这样才能表示的是一个已经被实例化的类模板。
当非模板类子类继承模板类父类时,应该明确父类中模板参数的类型,这样才可以确定子类的内存。否则子类也应定义为模板类,再用子类的模板参数给父类模板参数中确定模板类型,这样可以提高父类的灵活性。
template<class T>
class Fathername//父类
{
public:
T a;
};
template<class R,class P>
class Sonname :public Fathermame<R>//子类
{
public:
P b;
};
4.非类型的模板参数
类模板参数同样不局限与类型,也可以拥有非类型的模板参数。但是需要注意的是,浮点型,类型型不能当做非类型模板参数传递。
template<class T,int a=1;>
class Goodclassname
{
public:
int CoutInformation();
private:
T a;
};
template<class T,int a>
int Goodclassname<T,a>::CoutInformation()
{
cout<<a<<endl;
return a;
}