模板
模板
C++另一种编程思想称为泛型编程,主要利用的技术就是模板
C++提供两种模板机制:函数模板和类模板
函数模板
函数模板的基本概念
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template<typename T> //函数声明或定义 函数
示例:
template<typename T> ////利用模板提供通用的交换函数 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }
模板实现函数有两种:
- 自动类型推导
mySwap(a, b);
- 显示指定类型
mySwap<int>(a, b); //指定T为int
注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
普通函数和函数模板
普通函数与函数模板区别:
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
template<class T> T myAdd02(T a, T b) { return a + b; } void main() { int a = 10; int b = 20; char c = 'c'; myAdd01(a, b); //如果用自动类型推导,不会发生自动类型转换,即隐式类型转换 //myAdd02(a, c); //error,使用自动类型推导时,不会发生隐式类型转换 }
如果利用显示指定类型的方式,可以发生隐式类型转换
myAdd02<int>(a, c); //如果用显示指定类型,可以发生隐式类型转换
普通函数和函数模板的调用规则
如果函数模板和普通函数都可以实现,优先调用普通函数
可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
- 既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
局限性
模板的通用性并不是万能的。
例如:
template<class T> void function(T a, T b) { if(a = b) { ... } }
若传入自定义数据类型,将无法运行,为了解决这种问题,可使用模板的重载,可以为这些特定的类型提供具体化的模板。
class Entity { public: int m_X,m_Y; Entity(int x,int y) :m_X(x),m_Y(y){} }; //普通函数模板 template<class T> bool myCompare(T& a, T& b) { if (a == b){ return true; } else{ return false; } } //具体化(全特化) template<> bool myCompare(Entity& e1, Entity& e2) { if (e1.m_X == e2.m_X && e1.m_Y == e2.m_Y){ return true; } else{ return false; } } void main() { Entity e1(20, 10); Entity e2(20, 10); //自定义数据类型,不会调用普通的函数模板 //可以创建具体化的Entity数据类型的模板,用于特殊处理这个类型 bool ret = myCompare(e1, e2); }
注意事项:
类模板和函数模板都可以被全特化
函数模板可以实现重载,不能被偏特化,类模板可以被偏特化
类模板
类模板的基本概念
语法:
template<typename T> 类
示例:
template<class T> class Entity { public: T m_X, m_Y; Entity(T x, T y) :m_X(x), m_Y(y){} };
类模板和函数模板的区别
类模板与函数模板区别主要有两点:
- 类模板没有自动类型推导的使用方式
// Entity e(10,10); //错误类模板使用时候,不可以用自动类型推导 Person <int>p(10, 10); //必须使用显示指定类型的方式
- 类模板在模板参数列表中可以有默认参数
template<typename T = int>
类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机的区别:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
类模板对象做函数参数
一共有三种传入方式:
- 指定传入的类型---直接显示对象的数据类型
void Function(Entity<int> &e) {}
- 参数模板化 ---将对象中的参数变为模板进行传递
template <typename T> //函数模板和类模板结合使用 void Function(Entity<T>&e) { //可以使用typeid(T).name()查看传入的类型 std::cout << "T的类型为: " << typeid(T).name() << std::endl; }
- 整个类模板化---将这个对象类型模板化进行传递
template<typename T> void Function(T & p) { std::cout << "T的类型为: " << typeid(T).name() << std::endl; }
- 最常用的是第一种指定传入类型
类模板的继承
template<typename T> class Entity { public: T m_X,m_Y; };
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
class Player1 :public Entity<int> //必须指定一个类型 { };
如果想灵活指定出父类中T的类型,子类也需变为类模板
template<typename T> class Player2 :public Entity<T> { }; int main() { Player2<int> p; }
类模板成员函数类外实现
template<typename T> class Entity { public: T m_X; T m_Y; //成员函数类内声明 Entity(T x, T y); void Function(); }; //构造函数 类外实现 template<typename T> Entity<T>::Entity(T x, T y) :m_X(x),m_Y(y){} //成员函数 类外实现 template<typename T> void Entity<T>::Function() { } int main() { Entity<double> p(1.0, 1.0); p.Function(); }
类模板分文件编写
问题:
- 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决方法:
- 将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
#ifndef __ENTITY__HPP__ #define __ENTITY__HPP__ #include <string> template<typename T> class Entity { public: T m_X; T m_Y; Entity(T x, T y); void Function(); }; template<typename T> Entity<T>::Entity(T x, T y) :m_X(x),m_Y(y){} template<typename T> void Entity<T>::Function() { } #endif // !__ENTITY__HPP__
类模板与友元
全局函数类内实现 - 直接在类内声明友元即可,建议使用
template<typename T> class Entity { public: T m_X; T m_Y; Entiy(T x, T y) :m_X(x),m_Y(y){} //类内实现 friend void Function(Entity<T> & e) { } };
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
//全局函数配合友元类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元 template<typename T> class Entity; //如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到 template<typename T> void Function(Entity<T> & e) { }
模板特化
特化既是泛化的反面,对于某些特定的类型,我们可能希望进行特殊的处理,因此可使用模板特化使模板在接收特定的类型时可匹配到对应的特化模板。
-
类模板和函数模板都可以被全特化
-
函数模板可以实现重载,不能被偏特化,类模板可以被偏特化
模板全特化
全特化既是将确定模板中的所有模板参数。例如这有一个比较大小的模板仿函数,和一个需要比较的类:
class Entity
{
public:
int m_X;
int m_Y;
Entity(int x, int y) {}
void Function() {}
};
template <typename T>
struct Less
{
constexpr bool operator()(const T& _Left, const T& _Right) const
{
return _Left < _Right;
}
};
我们可以对仿函数进行全特化来特殊处理Entity类的比较:
template <>
struct Less<Entity>
{
constexpr bool operator()(const Entity& _Left, const Entity& _Right) const
{
if (_Left.m_X != _Right.m_X) {
return _Left.m_X < _Right.m_X;
}
else {
return _Left.m_Y < _Right.m_Y;
}
}
};
模板偏特化(partial specialization)
个数上的偏:
即特化模板中的部分模板参数,注意函数模板不可偏特化。
template <class _Alloc>
class vector<bool, _Alloc>
{};
例如这一段代码,便对vector容器进行偏特化,当使用者使用bool类型的vector容器时,就会匹配该模板特化,进行特殊处理。
范围上的偏:
template <typename T>
class example
{};
template <typename T>
class example<T*>
{};
此处便是对范围的偏特化,对于传入的所有类型,若该类型是指针,就会匹配该特化,将模板的范围缩小到指针类型。
模板模板参数(template template parameter)
即模板的参数是模板,这样做可以省去传入类型,如:
template <typename T,
template <typename U>
typename Container>
class XCls
{
private:
Container<T> c;
};
该模板中第二个参数Container即是模板模板参数,可在模板中将Container的模板参数指定为该模板的第一个模板参数T。
在使用时要注意若传入的模板的模板参数不止一个,如list容器有所存放元素的类型和分配器两个模板参数,使用时就不能如下使用,编译器会报错:
XCls<std::string, std::list> x1;
需要进行如下处理后才可使用:
template <typename T>
using Lst = std::list<T, std::allocator<T>>;
XCls<std::string, Lst> x1;