函数模板
基本使用
C++可以使用泛型编程,意思是1. 在函数使用的时候,先不指定参数的具体类型,后面可以传任意类型数据过来
基本语法:
template
返回值类型 函数(形参){}
typename可以换成class
template <class T>
void swap(T &a,T &b)
{
T temp=a;
a=b;
b=temp;
}
template <class T>
void func(){}
//使用时两种方式
//1.直接用,此时编译器会帮我们找到该参数的类型,但是T的类型必须一致
swap(1,2);//这是可以的
swap(1,'a');//这是不可以的,T类型不一致
//2.指定T类型
swap<int>(1,2);//这是可以的
func();//这是不可以的,因为func函数没有用到T,所以编译器没法找到T的类型
func<int>();//这是可以的,函数没有用到T,所以可以随意指定一个T的类型
注意事项
函数模板使用时需要注意参数的类型转换的问题,对于普通函数来说,调用函数时传入的参数类型和形参不一致时会把实参类型转换为形参类型,但是对于模板来说有两种情况:
- 当使用自动类型推导,由编译器推到T的类型时,不能发生参数类型转换(隐式类型转换)
- 当直接指定T的类型,则可以发生参数类型转换
int add01(int a,int b)
{return a+b;}
cout<<add01('a',10);//输出107,把a转换成ASIIC对应的数值再相加
template<class T>
T add02(T a,T b)
{return a+b;}
cout<<add02('a',10);//输出错误,自动推导出来两个类型,编译器无法知道使用哪个类型
cout<<add02<int>('a',10);//直接指定T为int类型,此时编译器会把字符a转换为ASIIC对应的数值再相加,结果为107
函数模板调用规则
- 如果函数模板和普通函数重名,参数个数相同,则优先调用普通函数
- 可以通过空模板参数列表强制调用函数模板
- 函数模板可以发生重载
- 如果函数模板发生更好的匹配,优先调用函数模板
int add(int a,int b)
{return a+b;}
template<class T>
T add(T a,T b)
{return a+b;}
template<class T>
T add(T a,T b,T c)
{return a+b+c;}
add(1,1);//调用普通函数
add<>(1,1);//空模板参数参数,调用函数模板
add(1,1,1);//调用重载函数模板
template<class T>
T func(T a,T b,T c)
{cout<<"函数模板";}
void func(int a,int b,int c)
{cout<<"普通函数";}
func('a','b','c');//调用函数模板,因为如果调用普通函数,还需要隐式类型转换
函数模板局限性、显示具体化(特化)
函数模板内部对于不同的数据类型可能不能统一操作,如判断输入的两个参数是不是相等,int可以直接判断,但是对于数组或者类来说,就不行了,这时我们需要具体化一个模板来实现
//template<> 开头来定义具体化模板
//也可以写成template<> bool isSame<Car>(Car &c1,Car &c2)
template<> bool isSame(Car &c1,Car &c2)
{
if(c1.id==c2.id){
cout<<"YES"<<endl;
}else{cout<<"NO"<<endl;}
}
显式实例化,隐式实例化
隐式实例化就是在本文最开始所使用的函数模板方法,和显示实例化的区别就是,隐式实例化只有在真正有函数在使用的时候才会生成一个函数,平时都只是当成一个模板存在,但是显示实例化之前必须有模板的声明,或者相同参数的模板特化。
//显示实例化
template int add<int>(int,int);
类模板
基本使用
和函数模板类似
template <class Type1,class Type2>
class Car{
public:
Type1 name;
Type2 num;
Car(Type1 name,Type2 num){
this->name=name;
this->num=age;
}
};
Car<string,int> c1("BWM",1);
无论是类模板还是函数模板,在调用的时候,尖括号都相当于是对模板定义的参数类型进行传值
和函数模板区别
类模板在使用时只有一种方式就是显示指定类型,没有自动类型推导
类模板的参数类型可以有默认值
template <class Type1,class Type2=int>
class Car{
public:
Type1 name;
Type2 num;
Car(Type1 name,Type2 num){
this->name=name;
this->num=age;
}
};
//在调用时,就可以这样,参数类型值传递可以不给包含默认参数类型传值
Car<string> c1("BWM",1);
类模板中成员函数调用时机
对于类模板中的成员函数来说,其不会在使用类模板创建好之后就直接把成员函数也直接生成,而是调用哪个函数就会生成哪个函数,没调用的我们就当作他还没存在,什么意思呢,看以下代码
class Car1{
public:
void showCar1(){cout<<"Car1"<<endl;}
};
class Car1{
public:
void showCar1(){cout<<"Car1"<<endl;}
};
template <class T>
class testCar{
public:
T obj;
void func1(){obj.showCar1();}
void func2(){obj.showCar2();}
};
//对于以下代码来说,如果一开始就认为func1和func2都存在的话,是会报错的,毕竟两个函数是属于不同的类里面的
testCar<Car1> t1;
t1.func1();//可以运行
t1.func2();//不可以运行,当指定obj对应的类之后,只能调用该类中包含的方法
对于以上代码,创建的testCar类对象不会管类里面的所有函数,而是在调用的时候再去创建这个函数,不调用的那些,就相当于类对象里面的小透明,不用管这些小透明里面的函数是不是属于其他类的。
类模板对象做函数参数
template <class T1,class T2=int>
class Car{
public:
T1 name;
T2 num;
Car(T1 name,T2 num){
this->name=name;
this->num=age;
}
};
Car<string,int> c1("BWM",1);
//第一种调用方式
//具体化类模板中参数类型
void showCar(Car<string,int> &c)
{
cout<<c.name<<endl;
}
showCar(c1);
//第二种方法,函数模板调用,把函数里面关于类模板中的参数抽象化,调用的时候编译器自动推导类型,类似于函数模板第一种调用方式
template <class T1,class T2>
void showCar(Car<T1,T2> &c)
{
cout<<c.name<<endl;
}
showCar(c1);
//第三种方法,类似于第二种,不同的是,把整个模板类都抽象化
template <class T>
void showCar(T &c)
{
cout<<c.name<<endl;
}
showCar(c1);
类模板的继承问题
当子类继承类模板的时候需要注意两点:
- 必须注明类模板中的数据类型
- 也可以把子类也变成类模板,灵活指定继承的父类模板的数据类型
class miniCar: public Car<string,int>
{
};
//当子类对象创建的时候,再指定T1和T2的类型,也就是指定了父类中的数据类型
template<class T1,class T2>
class miniCar: public Car<T1,T2>{};
类模板成员函数类外定义
//类外定义函数的时候,在写作用域的时候,就需要在作用域的类后面加上模板的参数列表
//函数参数和类内保持一致
//由于加上了类模板的参数列表,所以需要加上template <class T1,class T2...,class Tn>
template <class T1,class T2>
Car<T1,T2>::Car(T1 name,T2 num){
this->name=name;
this->num=age;
}
类模板的分文件编写
我们在做项目的时候,为了代码管理方便,往往都会把不同的类写在不同的文件中,包含头文件和源文件,对于类模板来说,由于其成员函数只有在调用的时候才生成,所以正常情况下分开编写时,编译器会直接忽视源文件中的代码,为了解决这个问题,可以在需要使用成员函数的文件里直接包含类模板的源文件,或者是在定义类模板的时候,选择把成员函数的定义和类模板声明写在一个文件里,并且命名为***.hpp,hpp后缀名是为了统一,大家约定俗成,不是必须的。
类模板的友元函数
类内实现
直接在类模板里面写好就行
template <class T1,class T2>
class Car{
//此书友元函数其实是一个全局函数,所以类模板作为友元函数参数是参考上文讲的第二种类模板做参数的方式
friend void viewCar(Car<T1,T2> &c)
{
cout<<c.name<<endl;
}
public:
T1 name;
T2 num;
Car(T1 name,T2 num){
this->name=name;
this->num=age;
}
};
类外实现
相比于类内实现复杂一点,分三步走
//第一步,函数模板声明
template <class T1,class T2> class Car;
//第二步,友元函数定义
template <class T1,class T2>
void viewCar<>(Car<T1,T2> &c)
{
cout<<c.name<<endl;
}
template <class T1,class T2>
class Car{
//第三步,类内声明友元函数的模板
//此处加上尖括号是让编译器知道,它的定义是一个函数模板
friend void viewCar<>(Car<T1,T2> &c);
public:
T1 name;
T2 num;
Car(T1 name,T2 num){
this->name=name;
this->num=age;
}
};