假如设计一个求两参数最大值的函数,在实践中我们可能需要定义四个函数:
这些函数几乎相同,唯一的区别就是形参类型不同。
需要事先知道有哪些类型会使用这些函数,对于未知类型这些函数不起作用。
其他可替代方法对比
替代方法缺点重载方式相同的代码复制了多次,有修改时候,多处相同代码都需要修改借助父类,子类继承父类1. 缺少类型检测的功能;2. 以后子类都需要继承父类,代码难以维护预处理命令1. 格式混乱; 2. 功能需求比较大时候,无法满足需求 2.模板的概念 所谓模板是一种使用无类型参数来产生一系列函数或类的机制。若一个程序的功能是对某种特定的数据类型进行处理,则可以将所处理的数据类型说明为参数,以便在其他数据类型的情况下使用,这就是模板的由来。模板是以一种完全通用的方法来设计函数或类而不必预先说明将被使用的每个对象的类型。通过模板可以产生类或函数的集合,使它们操作不同的数据类型,从而避免需要为每一种数据类型产生一个单独的类或函数。 3 模板的分类 模板分为函数模板(模子)和类模板(模子),允许用户分别用它们构造(套印)出(模板)函数和(模板)类。图显示了模板(函数模板和类模板),模板函数,模板类和对象之间的关系。
3.1 函数模板 定义格式 template <模板形参表><返回值类型> <函数名>(模板函数形参表){ //函数定义体}其中T为类型参数,它可用基本类型或用户自定义的类型。
模板形参表格式如下:
class <参数名>
或typename<参数名>
或<类型修饰> <参数名>
class 与typename 是没有却别的,class 出现的比较早,现在一般使用typename
模板函数的使用,只需要传入对应的值即可
std::cout << "max(7, 41):"<< ::max(7, 42) <<std::endl; // 显示指定类型 std::cout << "max(7, 42):"<< ::max<int>(7, 42) <<std::endl; std::cout << "max(7.0, 42.0):"<< ::max(7.0,42.0) <<std::endl; std::cout << "max<int>(7.0, 42.0):"<< ::max<int>(7.0,42.0) <<std::endl; std::cout << "max<double>(7.0, 42.0):"<< ::max<double>(7.0,42.0) <<std::endl std::string s1 = "aaaa"; std::string s2 = "aaaabb"; std::cout << "max(s1,s s2):"<< ::max(s1, s2) <<std::endl;两个参数类型不相同时候会报错,例如
max(7.0, 42) candidate template ignored: deduced conflicting types for parameter 'T' ('double' vs. 'int')inline T const& max(T const & a, T const & b)注:通常而言,并不是把模板编译成一个可以处理任何类型的单一实体;而是对于实例化模板参数的每种类型,都从模板产生一个不同的实体。这种用具体类型代替模板参数的过程叫做实例化,它产生了一个模板的实例。
于是,模板被编译了两次,分别发生在:
(1)实例化之前,先检查模板代码本身,查看语法是否正确;在这里会发现错误的语法,如遗漏分号等。
(2)在实例化期间,检查模板代码,查看是否所有的调用都有效。在这里会发现无效的调用,如该实例化类型不支持某些函数调用(该类型没有提供模板所需要使用到的操作)等。
关键字typename(或class)后面的T是类型参数。在实例化类定义中,欲采用通用数据类型的数据成员,成员函数的参数或返回值,前面需要加上T。
类模板的内部可以想其他类一样,声明成员变量和成员函数;
在成员函数的实现中必须要限定这个模板类,成员函数实现逻辑就是函数模板;
成员函数实现
// 入栈template <typename T>void Stack<T>::push(T const& e){ elems.push_back(e);}// 出栈template <typename T>void Stack<T>::pop(){ if (elems.empty()) { throw std::out_of_range("out of range"); } T e = elems.back(); elems.pop_back(); // 删除最后一个元素 return e;}// 返回栈顶原始template <typename T>T Stack<T>::top() const{ if (elems.empty()) { throw std::out_of_range("out of range"); } return elems.back(); // 返回最后一个元素的拷贝}类模板不代表一个具体的、实际的类,而代表一类类。实际上,类模板的使用就是将类模板实例化成一个具体的类,它的格式为:
类名<实际的类型>对象名;
例如,使用上面的类模板,创建模板参数为char、int型的对象,语句如下:
切记,要作为模板参数类型,唯一的要求就是:该类型必须提供被调用的所有操作 类模板的特化
特化 和 重载类似,重载是函数名相同,形参不同,特化就是类名相同,类的具体类型不同;
为了特化一个类模板,你必须在起始处声明一个template<>,接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:
进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代。如:
void Stack<std::string>::push(std::string const& elem){ elems.push_back(elem);} 局部特化上面的特化,类模板被具体类型代替、所有的成员函数被重新定义,这个叫做全特化;有时候要求模板参数仍由用户控制,这个叫做偏特化或者局部特化;
类模板
如下几种特化
// 两个模板参数具有相同的类型template <typename T>class Myclass<T, T> // {};// 第2个模板参数的类型是inttemplate <typename T>class Myclass<T, int>{};// 两个模板参数都是指针类型template <typename T1, typename T2>class Myclass<T1*, T2*> // 也可以使引用类型T&,常引用等{};创建对象
Myclass <int, float> m1; // 使用 Myclass<T1, T2>Myclass <float, float> m2; // 使用 Myclass<T, T>Myclass <float, int> m3; // 使用 Myclass<T, int> Myclass <int*, float*> m4; // 使用 Myclass<T1*, T2*>如果创建对象时候,出现一个对象对应两个模板类,就会报错;
4. more 4.1 优点: 灵活性, 可重用性和可扩展性;可以大大减少开发时间,模板可以把用同一个算法去适用于不同类型数据,在编译时确定具体的数据类型;模版模拟多态要比C++类继承实现多态效率要高, 无虚函数, 无继承; 缺点: 易读性比较不好,调试比较困难;模板的数据类型只能在编译时才能被确定;所有用基于模板算法的实现必须包含在整个设计的.h头文件中, 当工程比较大的时候, 编译时间较长;