C++模板及其原理
文章目录
泛型编程的概念
在谈模板之前先来了解一下“泛型编程”的概念。什么叫泛型,就是可以广泛的编程,所以可以初步的认为这种编程方法可以是的我们的编程复杂度降低,一定程度上提高编程效率。
在我写过的一篇博客中 手把手带你入门C++面向对象提到过C++中的函数重载及其原理,比如说交换函数:
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
void swap(double a, double b)
{
double tmp = a;
a = b;
b = tmp;
}
通过函数的重载可以使得在同名函数的存在,但是如果我还想交换char``float``long
等等其他类型的数据的时候,还要写很多的函数,这样还是会使得程序看起来很冗余,有没有一种更通用的方法,写一个通用的交换函数呢?因而C++再泛型编程上引进了模板的概念。就是给一个函数的模样,其中函数的内部实现与参数的类型无关,参数的类型只是一个模具,可以通过类型推导或者外界指定参数类型从而达到“一劳永逸”的效果。
模板分成两种:
1)函数模板
2)类模板
函数模板
函数模板的概念
函数模板是一个函数家族,该函数模板与参数类型无关,在使用函数模板的时候,根据实参的类型,实例化出对应类型版本的函数。
函数模板的格式
template <typename T1, typename T2, ... >
// 下面是函数的编写
注意:typename
是用定义模板的关键字,也可以用class
替代,T
只是一个名字,也可以去其他好理解的名字。
函数模板的演示:
例如:交换函数
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
函数模板的原理
其实函数模板就是一个模具,它本身并不是函数,是编译器通过对实参的推导然后在编译阶段实例化产生出了一份特定的类型的函数。如果函数模板的工作室在“照葫芦画瓢”,那么模板本身就是“葫芦”,而实例化产生的特定类型的函数就是“瓢”。
函数模板的实例化
函数模板在转换成某个特定类型的参数的过程叫做函数模板的实例化,实例化也分为两种:1)隐式实例化 2)显式实例化
隐式实例化
隐式实例化就是不明确的说明参数的类型,让函数模板自己类型推导
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int n1 = 1, n2 = 2;
double d1 = 1.1, d2 = 2.2;
// 通过自己的类型推导实例化函数
Swap(n1, n2);
Swap(d1, d2);
Swap(n1, d1);// 这种写法是错误的
return 0;
}
当想上面最后一个栗子一样,传入两个不同类型的参数的时候就会报错。
有三种解决方法:
1)强制类型转换
swap(n1, (int)d1);// 这样出入的参数就不是double而是int了
2)模板参数改变
template<typename T1, typename T2>
void Swap(T1& a, T2& b)
{
T1 tmp = a;
a = b;
b = tmp;
}
typename
有两个可以推导出两个参数,然后在a = b;
的时候T2
强转成T1
.
3)显式实例化函数
下面要介绍的。
显式实例化
在函数名后加上一个<>来制定转换的类型
Swap<int>(n1, d1);
像这样编译器就一定会生成T
为int
的函数。然后d1在传参的时候就会强转成int
.
函数参数的匹配原则
如果在调用函数的时候,有两个函数都可以调用,匹配原则一般是找更为合适的函数去匹配。
栗子1:
template<typename T>
void Swap(T& a, T& b)// 版本1
{
T tmp = a;
a = b;
b = tmp;
}
void Swap(int& a, int& b)// 版本2
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int n1 = 1, n2 = 2;
Swap(n1, n2); // 调用版本1
Swap<int>(n1, n2);// 调用版本2
return 0;
}
第一个调用因为存在int
特化版本所以就不会调用函数模板的示例化版本。第二个函数因为已经显式实例化函数模板了,所以调用版本2.
栗子2:
template<typename T1, typename T2>// 版本1
void Swap(T1& a, T2& b)
{
T1 tmp = a;
a = b;
b = tmp;
}
void Swap(int& a, int& b)// 版本2
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int n1 = 1;
double d1 = 2.0;
Swap(n1, d1);// 调用版本1
return 0;
}
这个行为可以调用两个参数,一个是用实例化的函数模板,一个是用强制转参数类型的函数。但是匹配原则会选择实例化版本,因为这样更为合适对应参数的类型,而不是改变参数的原有类型。
总结匹配原则:
1)对于非模板函数和同名的模板函数来说,如果其他的条件相同,在调用是会优先调用非模板函数而不是实例化出的模板函数。
2)如果函数模板可以匹配出更好的更可以匹配函数参数的函数则调用实例化出的模板函数。
3)模板函数不允许自动类型转换。
类模板
类模板的概念
既然函数可以模具,那类中有不同的需要统一的参数,所以就有了类模板。
类模板的格式
template<class T1, class T2, ...>
class 类名
{
// ...
};
注意:class
也可以换成typename
。
类模板的实例化
template <class T>
class Vector
{
public:
// 构造函数
Vector(size_t capacity = 10) :
_data(new T[capacity]),
_size(0),
_capacity(capacity) {}
// 析构函数
~Vector();
// 其他函数...
private:
T* _data;
size_t _size;
size_t _capacity;
};
// 在类外实现也需要写template<class T>
// Vector<T>才是类型,Vector不是
template<class T>
Vector<T>::~Vector()
{
delete[] _data;
_size = _capacity = 0;
}
类模板的示例化只能用显式实例化,不需要指明类型才可以。Vector不是类型,Vector才是类型
Vector<int> v1;
Vector<double> v2;
Vector<char> v3;
注意:
1)在类中声明的函数在类外实现的时候,每一个函数的前面都必须要加上template <class T>
这样的模板才可以
2)Vector
不是类型这只是一个模板,Vector<具体类型>
这样的才是一个特定类。