模板
模板是代码重用的一种很好的机制,比如当我们需要实现两个功能,但是这两个功能是有相似之处的,也就是实现体的基本逻辑是差不多的,但是传入的数据类型是不一样的,这时我们就可以定义一个公用的一个模板,然后就可以根据传进来的类型来实现多个功能,模板的实现就是泛型的基础。
定义模板语法:template <class或者typename T>这里的T就可以被替换成任意的数据类型
虽然两个名字可以通用
但是为了区分是模板函数还是模板类,一般把模板类写class,模板函数写typename
模板函数
当我们定义了一个模板函数时,在调用这个函数时有两种方式调用,一种是直接调用,然后编译器自动会根据你传递的参数来让T转化成你要的数据类型,还有一种是在调用是告诉编译器你传递的数据都是什么类型的,这里可以举例说明一下。
直接调用,编译器自动转换
#include <iostream>
#include <string>
using namespace std;
template<typename T>
void swapSum(T &a,T&b){
T temp=a;
a=b;
b=temp;
}
void test(){
int a=10;
int b=20;
swapSum(a,b);
cout<<"a="<<a<<"b="<<b<<endl;
}
int main(){
test();
return 0;
}
指定数据类型,也就是告诉编译器
#include <iostream>
#include <string>
using namespace std;
template<typename T>
void swapSum(T &a,T&b){
T temp=a;
a=b;
b=temp;
}
void test(){
int a=10;
int b=20;
swapSum<int>(a,b);
cout<<"a="<<a<<"b="<<b<<endl;
}
int main(){
test();
return 0;
}
使用函数模板时需要注意的是,如果不想自己给出类型的话,那么传递的函数类型必须是同一个数据类型,比如,int和char类型的,如果是普通函数,形参是int,实参是char,形参会自动传化为ASCII值,但是在函数模板中,如果也想这样让函数可以转化类型,则必须在调用函数模板时就给出明确的数据类型,才能发生隐式的数据类型转换,否则会报如下错误。
即使在函数模板的函数体内没有用到参数,也必须让模板知道T是转换成的是什么类型,所以这时也需要给出数据类型,否则也会报没有调用的匹配函数错误。
普通函数与模板函数调用规则
模板函数根本还是一个函数,那么它也是可以发生重载的,那么当普通函数和模板函数发生重载时,优先调用哪个呢,我们这里可以举个例子来试验一下。
#include <iostream>
#include <string>
using namespace std;
void swapSum(int a,int b){
cout<<"这是普通函数"<<endl;
}
template<typename T>
void swapSum(T a,T b){
cout<<"这是模板函数"<<endl;
}
void test(){
int a=97;
int b=98;
char c='a';
swapSum(a,b);
cout<<"a="<<a<<"b="<<b<<endl;
}
int main(){
test();
return 0;
}
我们可以看到当两个函数都可以调用时,会优先调用普通函数,如果模板函数更方便的话,会优先调用模板函数,也就是把上面的例子中普通函数的参数两个都改为char,编译器执行时还需要转换一次,所以索性就调用模板函数,如果想强行调用模板函数的话可以加一个空模板就可以强行调用模板函数。
函数模板是有一些局限性的,比如当我们需要判断两个成员是否相等时,我们传入的数据是基本类型才可以比较,但是如果传递的是自定义类型的话就会出错,除非我们重载了比较运算符或者还可以实现具体化的函数实现两个自定义类型的比较。
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(int age,string name){
this->age=age;
this->name=name;
}
int age;
string name;
};
template <typename T>
bool isSame(T p1,T p2){
if(p1==p2){
return true;
}
return false;
}
template <>bool isSame(person p1,person p2){
if(p1.name==p2.name&&p1.age==p2.age){
return true;
}
return false;
}
void test(){
person p1(10,"cat");
person p2(10,"cat");
cout<<isSame(p1,p2)<<endl;
}
int main(){
test();
return 0;
类模板
类模板和函数模板的定义是差不多的,就是看需要几个数据类型,就定义几个泛型
语法:template <class T1,class T2>
class person{ T1 age;
T2 name; }
类模板和函数模板也是有一些区别的,函数模板可以让编译器自动转化数据类型,但是类模板必须在实例化对象时给定传进来的数据类型,并且可以给默认参数
举例:person<int,string>(120,"cat");
或者在模板中 template<class T1=int,class T2=string>
普通类成员函数和类模板成员函数的创建区别在于,普通类成员函数是一开始就创建好了,但是类模板函数是调用时才创建,这就会导致在分文件编程时,即使包含了模板的头文件,也链接不到。
当类模板当作参数传递给函数时,我们可以用这几种方法来使形参接收实参。
#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class person{
public:
person(T1 age,T2 name){
this->age=age;
this->name=name;
}
void print(){
cout<<"name is"<<this->name<<endl<<"age is"<<this->age<<endl;
}
T1 age;
T2 name;
};
void func1(person<int,string>&p){
p.print();
}
template <typename T1,typename T2>
void func2(person<T1,T2>&p){
p.print();
}
template <class T>
void func3(T &p){
p.print();
}
void test(){
person <int,string>p1(10,"cat");
func1(p1);
func2(p1);
func3(p1);
}
int main(){
test();
return 0;
}
第一种是传递时需要把参数类型都表示清楚,第二种第三种都是通过函数模板来实现的。
类模板继承
当类模板继承时,在派生类中有两种解决方式。
第一种:class son :public father<需要转成的数据类型>
第二种:template <calss T1,class T2>
class son:public father<T1>然后这里的T1就会当作父类的泛型
因为用模板就是需要兼容性,所以一般都会使用第二种
#include <iostream>
#include <string>
using namespace std;
template <class T>
class father{
public:
T a;
};
template <class T1,class T2>
class son:public father<T1>{
public:
son(T1 a,T2 b){
this->a=a;
this->b=b;
}
void print(){
cout<<typeid(T1).name()<<endl<<typeid(T2).name()<<endl;
}
T1 b;
};
void test(){
son <int,int>s(10,10);
s.print();
}
int main(){
test();
return 0;
}
我们可以看到输出的就是传递的两个int类型
类模板外实现函数
这里的实现和普通类很相似,就是需要注意,在每个函数前都要加个模板,然后参数需要和模板类中的一致,并且表示作用域时需要在类后用尖括号来表示有哪几个参数。
#include <iostream>
#include <string>
using namespace std;
template <class T>
class father{
public:
T a;
};
template <class T1,class T2>
class son:public father<T1>{
public:
son(T1 a,T2 b);
void print();
T1 b;
};
template<class T1,class T2>
son<T1,T2>::son(T1 a,T2 b){
this->a=a;
this->b=b;
}
template<class T1,class T2>
void son<T1,T2>::print(){
cout<<typeid(T1).name()<<endl<<typeid(T2).name()<<endl;
}
void test(){
son <int,int>s(10,10);
s.print();
}
int main(){
test();
return 0;
}