c++之模版(函数模版+类模版)
一.函数模版【一个函数,不同类型的参数来调用】
函数模板是C++新增的一种性质,它允许只定义一次函数的实现,即可使用不同类型的参数来调用该函数(一个函数多用)。这样做可以减小代码的书写的复杂度,同时也便于修改(注:使用模板函数并不会减少最终可执行程序的大小,因为在调用模板函数时,编译器都根据调用时的参数类型进行了相应实例化)。
函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体;
意义:对于功能完全一样,只是参数类型不同的函数,能写一段通用代码是用于多种不同的数据类型,使代码的可重用性大大提高,从而提高软件的开发效率,达到“一类多用”的目的。
下面来看看函数模板的使用过程:
//.h struct job { char name[20]; int salary; }; template <class T> //函数模板声明,通用变量类型为T void swap(T &a, T &b); void showJob(const job &j);//打印job内容 //.cpp template<class T> //函数模板实现 void swap(T &a, T &b){ T temp; temp = a; a = b; b = temp; } void showJob(const job &j){ cout<<" "<<j.name<<" = "<<j.salary; } void main(void){ int a = 4; int b = 5; swap(a, b); job jobA = {"coder", 10000}; job jobB = {"manager", 1000}; swap(jobA, jobB); };
如果在上述job结构互换过程中只想互换salary,而不换其他成员变量值那么怎么办呢?C++中可以通过以下几种方法来解决这一问题。
1.显式具体化 (第三章节介绍实例化)
显式具体化也是基于函数模板的,只不过在函数模板的基础上,添加一个专门针对特定类型的、实现方式不同的具体化函数。
template<>void swap<job>(job &a, job &b){ int salary; salary = a.salary; a.salary = b.salary; b.salary = salary; }
2. 定义同名常规函数
void swap(job &a, job &b){ int salary; salary = a.salary; a.salary = b.salary; b.salary = salary; }
由于编译器在重载解析时,会选择最匹配函数定义,所以在调用swap(jobA, jobB)时,编译器会选择void swap(job &a, job &b)函数定义,而屏蔽了模板函数。同时,模板函数也可以重载,其操作与常规函数一致。
二.类模板
2.1 定义
template <class T> //声明一个模板,虚拟类型名为T。注意:这里没有分号。 class Compare //类模板名为Compare { public : Compare(T a,T b){ x=a;y=b; } T max(){ return (x>y)?x:y; } T min(){ return (x<y)?x:y; } private : T x,y; };
2.2 类模板与普通类比较 [增加类模版声明+虚拟类型参数名T]
1.声明类模板时要增加一行
template <class 类型参数名>
template意思是“模板”,是声明类模板时必须写的关键字。在template后面的尖括号内的内容为模板的参数表列, 关键字class(也可以用typename)表示其后面的是类型参数。
2.固定的类型名更换成虚拟类型参数名T
在建立类对象时,如果将实际类型指定为int型,编译系统就会用int取代所有的T,如果指定为float型,就用float取代所有的T,这样就能实现“一类多用”。
2.3 类模板成员函数体外实现
template <class T> T Compare<T>::max( ) {//类型+作用域引用 return (x > y)? x : y; }
2.4 类模版实例化
模板的实例化分为显示实例化和隐式实例化,不管是显示实例化或隐式实例化,最终生成的类或函数完全是按照模板的定义来实现:
- 显示实例化是明确告诉模板应该使用什么样的类型去生成具体的类或函数,手动指定;
- 隐式实例化是在编译的过程中由编译器来决定使用什么类型来实例化一个模板;
//常用模板类实例化方式:隐式实例化,由编译器根据指定类型生成模板类 Compare <int> cmp(4, 7); Compare <float> cmp(4.0, 7.0);
第三章节重点介绍模版实例化。
2.5.类模板的注意事项
1. 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2> class someclass { ... }; //*********** //在定义对象时分别代入实际的类型名 someclass<int, double> obj;
2. 和使用类一样,使用类模板时要 注意其作用域,只能在其有效作用域内用它定义对象。
3. 模板可以有层次, 一个类模板可以作为基类,派生出派生模板类。但有关这方面的知识实际应用较少。
三.模版实例化
模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程,模板的实例化分为隐式实例化和显示实例化:
- 对于函数模板而言,模板实例化之后,会生成一个真正的函数。
- 类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始化。
- 函数模板分为两种调用方式:一种是显示模板实参调用(显示调用),一种是隐式模板实参调用(隐式调用)。
- 类模板是没有隐式模板实参和显式模板实参使用的说法,因为类模板的使用必须显示指明模板实参,各个概念请勿混淆。
3.1 隐式实例化【编译器根据指定参数】
这是相对于模板显示实例化而言。在使用模板函数和模板类时,不存在指定类型的模板函数和模板类的实体时,由编译器根据指定类型参数隐式生成模板函数或者模板类的实体称之为模板的隐式实例化。
3.1.1函数模板隐式实例化 (函数调用时未发现匹配的函数,编译器会寻同名函数模版,若参数类型匹配,进行函数模版实例化)
函数模板隐式实例化指的是在发生函数调用的时候,如果没有发现相匹配的函数存在,编译器就会寻找同名函数模板,如果可以成功进行参数类型推演,就对函数模板进行实例化。
还有一种简介调用函数的情况,也可以完成函数模板的实例化。所谓的简介调用是指将函数入口地址传给一个函数指针,通过函数指针完成函数调用。如果传递给函数指针不是一个真正的函数,那么编译器就会寻找同名的函数模板进行参数推演,进而完成函数模板的实例化。参考如下示例:
#include <iostream> using namespace std; template <typename T> void func(T t){ cout<<t<<endl; } void invoke(void (*p)(int)){ int num=10; p(num); } int main(){ invoke(func); }
3.1.2 类模板隐式实例化 (模板类使用时才将模板实例化,常用方式)
类模板隐式实例化指的是在使用模板类时才将模板实例化,相对于类模板显示实例化而言的,如下程序:
#include <iostream> using namespace std; template<typename T>class A{ T num; public: A(){ num=T(6.6); } void print(){ cout<<"A'num:"<<num<<endl; } }; int main(){ A<int> a; //显示模板实参的隐式实例化 a.print(); }
3.1.3 函数模板隐式模板调用方式(参数推演)
在发生函数模板的调用时,不显示给出模板参数而经过参数推演,称之为函数模板的隐式模板实参调用(隐式调用)。如:
template <typename T> void func(T t){ cout<<t<<endl; } func(5);//隐式模板实参调用
3.2 显式实例化【外部实例化,不使用时已完成实例化】
显示实例化也称为外部实例化。在不发生函数调用的时候将函数模板实例化,或者在不使用类模板的时候将类模板实例化称之为模板显示实例化
3.2.1 函数模版显示实例化
对于函数模板而言,不管是否发生函数调用,都可以通过显示实例化声明将函数模板实例化,格式为:
template [函数返回类型] [函数模板名]<实际类型列表>(函数参数列表) //*****在定义时已经手动指定类型 template void func<int>(const int&); template<>void swap<job>(job &a, job &b)
3.2.2类模板的显示实例化
对于类模板而言,不管是否生成一个模板类的对象,都可以直接通过显示实例化声明将类模板实例化,格式为:
template class [类模板名]<实际类型列表> //********* template class theclass<int>;
3.2.3 函数模板显示模版调用
在发生函数模板的调用时,显示给出模板参数而不需要经过参数推演,称之为函数模板的显示模板实参调用(显示调用)。
显示模板实参调用在参数推演不成功的情况下是有必要的。考察如下程序:
#include <iostream> using namespace std; template <typename T> T Max(const T& t1,const T& t2){ return (t1>t2)?t1:t2; } int main(){ int i=5; //cout<<Max(i,'a')<<endl; //无法通过编译 cout<<Max<int>(i,'a')<<endl; //显示调用,通过编译 }
直接采用函数调用Max(i,’a’)会产生编译错误,因为i和’a’具有不同的数据类型,无法从这两个参数中进行类型推演。而采用Max< int>(i,’a’)调用后,函数模板的实例化不需要经过参数推演,而函数的第二个实参也可以由char转换为int型,从而成功完成函数调用。
编程过程中,建议采用显示模板实参的方式调用函数模板,这样提高了代码的可读性,便于代码的理解和维护。
3.3 总结
隐式实例化指的是:在使用模板之前,编译器不生成模板的声明和定义实例。只有当使用模板时,编译器才根据模板定义生成相应类型的实例(参数推演)。如:int i=0, j=1;swap(i, j); //编译器根据参数i,j的类型隐式地生成swap<int>(int &a, int &b)的函数定义。Array<int> arVal;//编译器根据类型参数隐式地生成Array<int>类声明和类函数定义。
显式实例化:当显式实例化模板时,在使用模板之前,编译器根据显式实例化指定的类型生成模板实例(参数不推演)。如前面显示实例化(explicit instantiation)模板函数和模板类。其格式为:template typename function<typename>(argulist);template class classname<typename>;显式实例化只需声明,不需要重新定义。编译器根据模板实现实例声明和实例定义。
显示具体化:对于某些特殊类型,可能不适合模板实现,需要重新定义实现,此时可以使用显示具体化(explicite specialization)。显示实例化需重新定义。格式为:template<> typename function<typename>(argu_list){...};template<> class classname<typename>{...};
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
参考文章:
链接1:https://www.cnblogs.com/cthon/p/9203718.html
链接2:https://blog.csdn.net/EmSoftEn/article/details/50421124?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-50421124-blog-108028024.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-50421124-blog-108028024.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=2