泛型编程
当我们想要实现不同类型的加法函数时,我们则要根据需求写出对应的函数:
int Add(int a, int b)//整型加法
{
return a + b;
}
double Add(double a, double b)//浮点型加法
{
return a + b;
}
这时我们用到的就是函数重载
,但是会有几个缺点:
- 重载的函数仅仅是类型不同,代码复用率也比较低,
一旦有新类型出现时,就需要用户自己增加对应的函数。
代码的可维护性较低
,一个出错可能造成所有的重载函数均出错。
此时就会有人想到,能不能给编译器一个模子,让编译器根据不同类型利用这个模子来生成代码。就像活字印刷术一样,我想印什么字,就将那个字放入模具中印刷即可。在C++中这就是模板。
所谓泛型编程
就是编写与类型无关的通用代码
,是代码复用的一种手段
。而模板就是泛型编程的基础。
对于模板,C++中分为了两种:
- 函数模板
- 类模板
函数模板
1.函数模板的概念
所谓函数模板
,是建立一个通用函数,不指定函数类型和参数类型
,而用一个虚拟的类型表示。在调用函数时,用实参的类型取代模板中的虚拟类型。
2.函数模板的格式用法
- template :是模板的关键字
- typename :关键字,表示其后的标识符代表类型参数,调用时根据实参的类型确定形参的类型。
- 形式为:
template < typename T>
或者有多个参数时template < typename T1, typename T2,…,typename Tn> - T是模板中的参数名,可以自己定义
使用格式为:
template < typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表)
{}
如对于加法的函数模板为:
template<typename T>
T Add(T a, T b)
{
return a+b;
}
此时我们就能发现,我们原先要写两个函数才能实现的加法现在只需要一个函数模板就能实现,方便了很多。
注意:typename可以用class代替,但不能用struct。
2.函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,所以模板就是将本来应该我们要做的重复的事情交给了编译器。
在编译器编译时,对于函数模板的使用,编译器会根据需要传入的实参类型来推演生成对应类型的函数以供调用。
所以实际参数调用时,调用的是模板生成的对应函数,
而非模板本身!
3. 函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。
函数模板的实例化分为两种:
隐式实例化
就是让编译器根据实参推演模板参数的实际类型,就如上面写的代码所示,当然还有一种情况编译器会报错,如下:
当我们想要浮点型加整型时,则不能通过编译,因为在编译时,编译器看到这个实例化,需要推演其实参类型
,通过1.1就会将T推演为double,通过2就会将T推演为int类型,但模板参数列表中只有一个T,编译器无法确实T是double还是int类型而报错。
要注意的是:在模板中,编译器一般不会进行类型转换的操作。
此时有3种解决办法:
a.用户自己强制转换:
template<typename T>
T Add(T a, T b)
{
return a + b;
}
int main()
{
//用户自己强制类型转换
cout << Add((int)1.1, 2) << endl;
return 0;
}
b.再写一个由两个参数构成的函数模板:
template<typename T1, typename T2>
auto Add(T1 a, T2 b)
{
return a + b;
}
int main()
{
//cout << Add((int)1.1, 2) << endl;
cout << Add(1.1, 2) << endl;
return 0;
}
c.使用显示实例化。
显示实例化
就是在函数名后面加上<模板参数类型>。
例:
//显示实例化
Add<int>(1.1, 2);
要注意的是:如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器会报错。
4.模板参数的匹配原则
我们来看下面一个代码:
template<typename T>
T Add(T a, T b)//通用加法函数
{
cout << "T Add(T a, T b)" << endl;
return a + b;
}
template<typename T1, typename T2>
auto Add(T1 a, T2 b)//通用加法函数
{
cout << "auto Add(T1 a, T2 b)" << endl;
return a + b;
}
int Add(int a, int b)//普通加法函数
{
cout << "int Add(int a, int b)" << endl;
return a + b;
}
int main()
{
Add(1.1, 2);
Add(2, 3);
Add(1.1, 2.2);
return 0;
}
结果如下:
我们发现,它每一个匹配使用的函数都不一样,这里有几个规律:
- 各种模板函数普通函数都有的情况下,
优先匹配普通函数+参数匹配。
- 若没有普通函数,
优先考虑参数匹配+函数模板。
- 只有一个的情况下,类型转换将就使用。
要注意的是:函数模板不允许自动类型转换,但普通函数可以自动类型转换。
类模板
1.类模板的定义格式
定义格式如下:
template<class T1, class T2,..., class Tn>
class 类模板名
{
//类成员定义
}
2.类模板的使用方法
我们来写一个动态顺序表:
namespace a
{
template<class T>
class Vector
{
public:
Vector(size_t capacity = 10)
:_arr(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
~Vector();
//...
private:
T* _arr;
size_t _size;
size_t _capacity;
};
template<class T>
Vector<T>::~Vector()
{
if (_arr)
{
delete[] _arr;
}
_size = _capacity = 0;
}
}
int main()
{
a::Vector<int> t(10);
return 0;
}
我们发现在类模板中函数在类外定义时
,需要把模板参数列表也就是template< class T>加上。
在类模板实例化时,需要在类模板名字后面跟上<>,然后将实例化的类型放在<>里面,也就是vector<int>才是类型
。
类模板名字不是真正的类,而实例化的结果才是真正的类
。
感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。