一、概述
vector类模版 通过<类型> 生产真正的类vector
- 所谓泛型编程,是以独立于任何特定类型的方式编写代码。使用泛型编程时,我们需要提供具体程序实例所操作的类习惯或者值。
- 模板是泛型编程的基础。模板是创建类或者函数的蓝图或公式,我们给这些蓝图或者公式提供足够的信息,让这些蓝图或者公式真正的转变为具体的类或者函数,这种转变发生在编译时。
- 模板支持将类型作为参数的程序设计方式,从而实现了对泛型程序设计的直接支持。也就是说,C++模板机制允许在定义类、函数时将类型作为参数。
- 模板一般分为函数模板和类模板 本节课主要讲函数模板。
二、函数模版的定义
int addFunc(int a,int b)
{
int add = a+b;
return add;
}
double addFunc(double a,double b)
{
double add = a+b;
return add;
}
为了避免重复代码,通过写通用的模版来实现。
template<typename T> //typename/class
T addFunc(T a,T b)
{
T add = a+b;
return add;
}
- 模板定义是用template关键字开头,后面跟<>,<>里面叫模板参数列表(模板实参),如果模板参数列表里面有多个参数,则用逗号分开。 里面必须至少得有一个模板参数,模板参数前面有个typename/class(不是用来定义类的)关键字。template 这种写法大家硬记。如果模板参数列表里面有多个模板参数,那你就要用多个typename/class: <typename T,typename Q>。
- 模板参数列表里面表示在函数定义中要用到的"类型"或者"值",也和函数参数列表类似。那我们用的时候,有的时候得指定模板实参给它,指定的时候我们要用<>把模板实参包起来,有的时候又不需要指定模板实参给它,系统自己能够根据一些信息推断出来,后续我们会举例
- addFunc这个函数声明了一个名字为T的类型参数。这里注意:T实际是类型,这个T到底代表的是啥类型,编译器在编译的时候会根据针对addFunc()的调用来确定。
三、函数模版的使用
函数模板调用和函数调用区别不大,调用的时候,编译器会根据你调用这个函数模板时的实参去推断模板参数列表里的参数(形参)的类型。大家一定要注意措辞:模板参数有时候是推断出来的(有的时候是需要你提供),推断的依据是什么呢? 是根据你调用这个函数的实参来推断的。
当然有的时候,光凭借函数实参是推断不出来模板参数,这个时候就要用<>来主动的提供模板参数了。
template<typename T> //typename/class
T addFunc(T a,T b)
{
T add = a+b;
return add;
}
int main()
{
int add = addFunc(3,1);
//系统默认3、1是int,系统推断模版参数T是int类型
//编译器在推断出来这个模板的形参类型之后,编译器就为我们实例化一个特定版本的函数。
double adddouble = addFunc(3.0f,1.0f);
//系统推断模版参数T是double类型
//int add = addFunc(3,1.0f);系统不知道推断是int还是double,报错
return 1;
}
四、非类型模版参数
- 因为T前面有个typename/class,这表示T代表一个类型,是一个类型参数。
- 在模板参数列表里边,还可以定义非类型参数; 非类型参数代表的是一个值。既然非类型代表一个值,那么肯定不能用typename/class这种关键字来修饰这个值。我们当然要用以往学习过的传统类型名来指定非类型参数了。比如你非类型参数Q如果是个整型, int Q等。
template<typename T, int Q>
T funcadd2(T a, T b);
当模板被实例化时,这种非类型模板参数的值或者用户提供的,或者是编译器推断的,都有可能。
但是,这些值必须都得是常量表达式,因为实例化这些模板是编译器在编译时实例化的。
std::cout << funcadd2<12, 13>() << endl; // 显示的指定模板参数 在<>中提供额外的信息。
template<unsigned L1,unsigned L2> // 非类型模版参数
inline // 模板函数可以是inline的,inline的位置放在模板参数列表之后,
int charcomp(const char(&p1)[L1],const char(&p2)[L2])
{
return strcmp(p1,p2);
}
//int charcomp(const char(&p1)[5], const char(&p2)[5])
//{
// return strcmp(p1, p2);
//}
template<int a,int b>
int funcadd2()
{
int add = a+b;
return add;
}
template<typename T,int a,int b>
int funcadd3(T c)
{
int add = (int)c+a+b;
return add;
}
int main() {
int result = funcadd2<5,2>(); //显式的指定模版参数
int a = 5;
//int result = funcadd2<a,2>();//报错,必须在编译的时候就要确认用,编译时候推断的,在运行时会才确认会报错
//template<typename T,int a,int b>
//int funcadd3(T c)
int result = funcadd3<int,10,10>(20);
//系统会以我们用<>传递的类型为准,而不是用20来推断什么类型。
int result2 = funcadd3<double,10,10>(20);
//虽然是double,但是系统会根据传入的20来推断模版参数类型
cout << result2 << endl;
int com = charcomp("test1","test2");
//没有提供参数,系统会用test1,test2的长度取代L1,L2
// int com2 = charcomp<5,5>("test1","test2");最好别这么干
cout << com << endl;
return 1;
}
结论:
- 模板定义并不会导致编译器生成代码,只有在我们调用这个函数模板时。
- 使编译器为我们实例化了一个特定版本的函数之后,编译器才会生成代码。
- 编译器生成代码的时候,需要能够找到函数的函数体,所以函数模板的定义通常都是在.h文件中。